From 0f5773ef98d9efc221674eda82b9629a9f5a13f9 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 1 Oct 2025 15:20:49 +0000 Subject: [PATCH 1/2] feat: add Snapshot Isolation to Multiplexed Sessions --- Spanner/src/Database.php | 40 +++++- Spanner/src/Instance.php | 12 +- Spanner/src/Operation.php | 1 + Spanner/src/SpannerClient.php | 11 ++ Spanner/src/Transaction.php | 13 ++ Spanner/src/TransactionConfigurationTrait.php | 6 +- Spanner/tests/Snippet/DatabaseTest.php | 5 + Spanner/tests/System/TransactionTest.php | 6 + Spanner/tests/Unit/DatabaseTest.php | 126 +++++++++++++++++- Spanner/tests/Unit/TransactionTypeTest.php | 1 + 10 files changed, 213 insertions(+), 8 deletions(-) diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index e725fff88bc8..57ce9911ac04 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -51,6 +51,7 @@ use Google\Cloud\Spanner\V1\Mutation; use Google\Cloud\Spanner\V1\Mutation\Delete; use Google\Cloud\Spanner\V1\Mutation\Write; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\Cloud\Spanner\V1\TypeCode; use Google\LongRunning\ListOperationsRequest; use Google\LongRunning\Operation as OperationProto; @@ -59,6 +60,7 @@ use Google\Protobuf\Value; use Google\Rpc\Code; use Psr\Cache\CacheItemPoolInterface; +use InvalidArgumentException; /** * Represents a Cloud Spanner Database. @@ -123,6 +125,7 @@ class Database private bool $returnInt64AsObject; private CacheItemPoolInterface $cacheItemPool; private array $info; + private int $isolationLevel; private const MUTATION_SETTERS = [ 'insert' => 'setInsert', @@ -132,6 +135,7 @@ class Database 'delete' => 'setDelete' ]; + /** * Create an object representing a Database. * @@ -156,6 +160,8 @@ class Database * @type string $databaseRole The user created database role which * creates the session. * @type array $database The database info. + * @type int $isolationLevel The level of Isolation for the transactions executed by this Client's instance. + * **Defaults to** IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED * } */ public function __construct( @@ -187,6 +193,8 @@ public function __construct( $this->optionsValidator = new OptionsValidator($serializer); $this->directedReadOptions = $instance->directedReadOptions(); + $this->returnInt64AsObject = $returnInt64AsObject; + $this->isolationLevel = $options['isolationLevel'] ?? IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED; } /** @@ -802,6 +810,8 @@ public function snapshot(array $options = []): TransactionalReadInterface * Session labels may be applied using the `labels` key. * @type string $tag A transaction tag. Requests made using this transaction will * use this as the transaction tag. + * @type int $isolationLevel The level of Isolation for the transactions executed by this Client's instance. + * **Defaults to** IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED * } * @return Transaction * @throws \BadMethodCallException If attempting to call this method within @@ -813,7 +823,9 @@ public function transaction(array $options = []): Transaction throw new \BadMethodCallException('Nested transactions are not supported by this client.'); } - $options['transactionOptions'] = $this->initReadWriteTransactionOptions(); + $options['transactionOptions'] = $this->configureReadWriteTransactionOptions([ + 'isolationLevel' => $options['isolationLevel'] ?? $this->isolationLevel + ]); return $this->operation->transaction($this->session, $options); } @@ -899,6 +911,9 @@ public function transaction(array $options = []): Transaction * Session labels may be applied using the `labels` key. * @type string $tag A transaction tag. Requests made using this transaction will * use this as the transaction tag. + * @type array transactionOptions Options for the transaction. + * {@see \Google\Cloud\Spanner\V1\TransactionOptions} + * for available options * } * @return mixed The return value of `$operation`. * @throws \RuntimeException If a transaction is not committed or rolled back. @@ -913,7 +928,12 @@ public function runTransaction(callable $operation, array $options = []): mixed $retrySettings = $options['retrySettings'] ?? ['maxRetries' => self::MAX_RETRIES]; $maxRetries = $retrySettings instanceof RetrySettings ? $retrySettings->getMaxRetries() - : $retrySettings['maxRetries']; + : $retrySettings['maxRetries']; + + + if (!isset($options['transactionOptions']['isolationLevel'])) { + $options['transactionOptions']['isolationLevel'] = $this->isolationLevel; + } // Configure necessary readWrite nested and base options $options['transactionOptions'] = $this->configureReadWriteTransactionOptions( @@ -924,6 +944,10 @@ public function runTransaction(callable $operation, array $options = []): mixed $startTransactionFn = function ($options) use (&$attempt) { // Initial attempt requires to set `begin` options (ILB). if ($attempt === 0) { + if (!isset($options['transactionOptions']['isolationLevel'])) { + $options['transactionOptions']['isolationLevel'] = IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED; + } + // Partitioned DML does not support ILB. if (!isset($options['transactionOptions']['partitionedDml'])) { $options['begin'] = $options['transactionOptions']; @@ -1614,7 +1638,7 @@ public function delete(string $table, KeySet $keySet, array $options = []): Time * timestamp. * @type Duration $exactStaleness Represents a number of seconds. Executes * all reads at a timestamp that is $exactStaleness old. - * @type bool $begin If true, will begin a new transaction. If a + * @type bool|array $begin If true, will begin a new transaction. If a * read/write transaction is desired, set the value of * $transactionType. If a transaction or snapshot is created, it * will be returned as `$result->transaction()` or @@ -1665,6 +1689,10 @@ public function execute($sql, array $options = []): Result $options['transactionContext'] ) = $this->transactionSelector($options); + if (isset($options['transaction']['readWrite'])) { + $options['transaction']['begin']['isolationLevel'] ??= $this->isolationLevel; + } + $options['directedReadOptions'] = $this->configureDirectedReadOptions( $options, $this->directedReadOptions @@ -1890,19 +1918,23 @@ public function batchWrite(array $mutationGroups, array $options = []): \Generat public function executePartitionedUpdate($statement, array $options = []): int { unset($options['requestOptions']['transactionTag']); + if (isset($options['transactionOptions']['isolationLevel'])) { + throw new InvalidArgumentException('Partitioned DML cannot be configured with an isolation level'); + } $beginTransactionOptions = [ 'transactionOptions' => [ 'partitionedDml' => [], ] ]; + if (isset($options['transactionOptions']['excludeTxnFromChangeStreams'])) { $beginTransactionOptions['transactionOptions']['excludeTxnFromChangeStreams'] = $options['transactionOptions']['excludeTxnFromChangeStreams']; unset($options['transactionOptions']); } - $transaction = $this->operation->transaction($this->session, $beginTransactionOptions); + $transaction = $this->operation->transaction($this->session, $beginTransactionOptions); return $this->operation->executeUpdate($this->session, $transaction, $statement, [ 'statsItem' => 'rowCountLowerBound', 'route-to-leader' => true, diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index f0d4c2f4bcda..1da5d6a0b9b1 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -40,6 +40,7 @@ use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; use Google\Cloud\Spanner\Session\SessionCache; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\LongRunning\ListOperationsRequest; use Google\LongRunning\Operation as OperationProto; use Psr\Cache\CacheItemPoolInterface; @@ -73,6 +74,11 @@ class Instance private bool $returnInt64AsObject; private CacheItemPoolInterface|null $cacheItemPool; + /** + * @var int + */ + private $isolationLevel; + /** * Create an object representing a Cloud Spanner instance. * @@ -97,6 +103,8 @@ class Instance * returned as a {@see \Google\Cloud\Core\Int64} object for 32 bit platform * compatibility. **Defaults to** false. * @type CacheItemPool $cacheItemPool + * @type int $isolationLevel The level of Isolation for the transactions executed by this Client's instance. + * **Defaults to** IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED * } * @param array $info A representation of the instance object. */ @@ -118,6 +126,7 @@ public function __construct( $this->cacheItemPool = $options['cacheItemPool'] ?? null; $this->projectName = InstanceAdminClient::projectName($projectId); $this->optionsValidator = new OptionsValidator($serializer); + $this->isolationLevel = $options['isolationLevel'] ?? IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED; } /** @@ -551,7 +560,8 @@ public function database(string $name, array $options = []): Database $options + [ 'routeToLeader' => $this->routeToLeader, 'defaultQueryOptions' => $this->defaultQueryOptions, - 'returnInt64AsObject' => $this->returnInt64AsObject, + 'returnInt64AsObject' => $this->returnInt64AsObject, + 'isolationLevel' => $this->isolationLevel, ] ); } diff --git a/Spanner/src/Operation.php b/Spanner/src/Operation.php index f3395a6a52aa..dc192332459d 100644 --- a/Spanner/src/Operation.php +++ b/Spanner/src/Operation.php @@ -533,6 +533,7 @@ public function read( * that commit mutations but do not perform any reads or queries. If not supplied, * one of the mutations from the mutation set will be selected and sent as a part of * this request. + * @type int $isolationLevel The level of Isolation for the transactions executed by this Client's instance. * } * @return Transaction */ diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index eb10b4d35ba6..03f66584f1a1 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -39,6 +39,7 @@ use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Middleware\SpannerMiddleware; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\LongRunning\Operation as OperationProto; use Google\Protobuf\Duration; use Psr\Cache\CacheItemPoolInterface; @@ -131,6 +132,11 @@ class SpannerClient private array $defaultQueryOptions; private CacheItemPoolInterface|null $cacheItemPool; + /** + * @var int + */ + private $isolationLevel; + /** * Create a Spanner client. Please note that this client requires * [the gRPC extension](https://cloud.google.com/php/grpc). @@ -180,6 +186,8 @@ class SpannerClient * @type string $universeDomain The expected universe of the credentials. Defaults to * "googleapis.com" * @type CacheItemPoolInterface $cacheItemPool + * @type int $isolationLevel The level of Isolation for the transactions executed by this Client's instance. + * **Defaults to** IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED * } * @throws GoogleException If the gRPC extension is not enabled. */ @@ -196,6 +204,7 @@ public function __construct(array $options = []) 'emulatorHost' => $emulatorHost, 'queryOptions' => [], 'cacheItemPool' => null, + 'isolationLevel' => IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED, ]; $this->returnInt64AsObject = $options['returnInt64AsObject']; @@ -254,6 +263,7 @@ public function __construct(array $options = []) $this->projectName = InstanceAdminClient::projectName($this->projectId); $this->cacheItemPool = $options['cacheItemPool']; + $this->isolationLevel = $config['isolationLevel']; } /** @@ -561,6 +571,7 @@ public function instance(string $name, array $instance = []): Instance 'defaultQueryOptions' => $this->defaultQueryOptions, 'returnInt64AsObject' => $this->returnInt64AsObject, 'cacheItemPool' => $this->cacheItemPool, + 'isolationLevel' => $this->isolationLevel, ], $instance, ); diff --git a/Spanner/src/Transaction.php b/Spanner/src/Transaction.php index 35e7ddf1cce4..177b867a74b1 100644 --- a/Spanner/src/Transaction.php +++ b/Spanner/src/Transaction.php @@ -93,6 +93,8 @@ class Transaction implements TransactionalReadInterface * @type array $singleUse The singleUse Transaction options. See {@see V1\TransactionOptions}. * @type array $requestOptions See {@see V1\RequestOptions}. * @type array $transactionOptions See {@see V1\TransactionOptions}. + * @type int $isolationLevel level of Isolation for this transaction instance + * **Defaults to** IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED * } * @param ValueMapper $mapper Consumed internally for properly map mutation data. * @throws \InvalidArgumentException if a tag is specified on a single-use transaction. @@ -241,6 +243,17 @@ public function executeUpdate(string $sql, array $options = []): int . ' This option should be set at the transaction level.' ); } + + if ($this->type() === self::TYPE_SINGLE_USE && + isset($options['transaction']['begin']['isolationLevel']) || + isset($options['transaction']['single_use']['isolationLevel']) + ) { + throw new ValidationException( + 'The isolation level can only be applied to read/write transactions.' . + 'Single use transactions are not read/write', + ); + } + $options = $this->buildUpdateOptions($options); return $this->operation ->executeUpdate($this->session, $this, $sql, $options); diff --git a/Spanner/src/TransactionConfigurationTrait.php b/Spanner/src/TransactionConfigurationTrait.php index 50ae1147e2ab..0f1b3bc6d4c7 100644 --- a/Spanner/src/TransactionConfigurationTrait.php +++ b/Spanner/src/TransactionConfigurationTrait.php @@ -149,9 +149,13 @@ private function configureReadWriteTransactionOptions(array|TransactionOptions $ { $excludeTxn = $options instanceof TransactionOptions ? $options->getExcludeTxnFromChangeStreams() - : $options['excludeTxnFromChangeStreams'] ?? null; + : $options['excludeTxnFromChangeStreams'] ?? null; + $isolationLevel = $options instanceof TransactionOptions + ? $options->getIsolationLevel() + : $options['isolationLevel'] ?? null; return array_filter([ 'excludeTxnFromChangeStreams' => $excludeTxn, + 'isolationLevel' => $isolationLevel, ]) + $this->initReadWriteTransactionOptions(); } diff --git a/Spanner/tests/Snippet/DatabaseTest.php b/Spanner/tests/Snippet/DatabaseTest.php index fd6283c6cfee..1e5d33c07ce4 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -61,6 +61,7 @@ use Google\Cloud\Spanner\V1\RollbackRequest; use Google\Cloud\Spanner\V1\Session; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\LongRunning\Client\OperationsClient; use Google\LongRunning\ListOperationsResponse; use Google\LongRunning\Operation; @@ -491,6 +492,10 @@ public function testRunTransactionRollback() $this->serializer->encodeMessage($request)['transaction']['begin']['readWrite'], ['readLockMode' => 0, 'multiplexedSessionPreviousTransactionId' => ''] ); + $this->assertEquals( + $this->serializer->encodeMessage($request)['transaction']['begin']['isolationLevel'], + IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED + ); return true; }), Argument::type('array') diff --git a/Spanner/tests/System/TransactionTest.php b/Spanner/tests/System/TransactionTest.php index 4986fd0c9cf7..54ca8607402f 100644 --- a/Spanner/tests/System/TransactionTest.php +++ b/Spanner/tests/System/TransactionTest.php @@ -28,6 +28,7 @@ use ReflectionClass; use Google\Cloud\Spanner\V1\ReadRequest\LockHint; use Google\Cloud\Spanner\V1\ReadRequest\OrderBy; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; /** * @group spanner @@ -426,6 +427,11 @@ public function testRunTransactionILBWithMultipleOperations() 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), 'birthday' => new Date(new \DateTime()) + ], + 'transaction' => [ + 'begin' => [ + 'isolationLevel' => IsolationLevel::REPEATABLE_READ, + ] ] ] ); diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index abea39e5366d..0595c8f14eae 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -73,6 +73,7 @@ use Google\Cloud\Spanner\V1\StructType; use Google\Cloud\Spanner\V1\StructType\Field; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\Cloud\Spanner\V1\TransactionSelector; use Google\Cloud\Spanner\V1\Type as TypeProto; use Google\Protobuf\Duration; @@ -108,7 +109,7 @@ class DatabaseTest extends TestCase const TRANSACTION_TAG = 'my-transaction-tag'; const TEST_TABLE_NAME = 'Users'; const TIMESTAMP = '2017-01-09T18:05:22.534799Z'; - const BEGIN_RW_OPTIONS = ['begin' => ['readWrite' => []]]; + const BEGIN_RW_OPTIONS = ['begin' => ['readWrite' => [], 'isolationLevel' => 0]]; private const DIRECTED_READ_OPTIONS_INCLUDE_REPLICAS = [ 'includeReplicas' => [ @@ -936,6 +937,38 @@ public function testTransaction() $this->assertInstanceOf(Transaction::class, $t); } + public function testTransactionWithIsolationLevel() + { + $this->connection->beginTransaction(Argument::allOf( + Argument::withEntry('session', $this->session->name()), + Argument::withEntry( + 'database', + DatabaseAdminClient::databaseName( + self::PROJECT, + self::INSTANCE, + self::DATABASE + ) + ), + Argument::withEntry('requestOptions', [ + 'transactionTag' => self::TRANSACTION_TAG, + ]), + Argument::withEntry('transactionOptions', [ + 'readWrite' => [], + 'isolationLevel' => IsolationLevel::REPEATABLE_READ, + ]) + )) + ->shouldBeCalled() + ->willReturn(['id' => self::TRANSACTION]); + + $this->refreshOperation($this->database, $this->connection->reveal()); + + $t = $this->database->transaction([ + 'tag' => self::TRANSACTION_TAG, + 'isolationLevel' => IsolationLevel::REPEATABLE_READ + ]); + $this->assertInstanceOf(Transaction::class, $t); + } + public function testTransactionNestedTransaction() { $this->expectException(\BadMethodCallException::class); @@ -1267,6 +1300,34 @@ public function testExecute() $this->assertEquals(10, $rows[0]['ID']); } + public function testExecuteWithIsolationLevel() + { + $sql = 'SELECT * FROM Table'; + + $this->connection->executeStreamingSql(Argument::allOf( + Argument::withEntry('sql', $sql), + Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]), + Argument::withEntry('transaction', [ + 'begin' => [ + 'readWrite' => [], + 'isolationLevel' => IsolationLevel::REPEATABLE_READ + ] + ]) + ))->shouldBeCalled()->willReturn($this->resultGenerator()); + + $this->refreshOperation($this->database, $this->connection->reveal()); + + $res = $this->database->execute($sql, [ + 'transactionType' => SessionPoolInterface::CONTEXT_READWRITE, + 'begin' => [ + 'isolationLevel' => IsolationLevel::REPEATABLE_READ + ] + ]); + $this->assertInstanceOf(Result::class, $res); + $rows = iterator_to_array($res->rows()); + $this->assertEquals(10, $rows[0]['ID']); + } + public function testExecuteWithSingleSession() { $sql = 'SELECT * FROM Table'; @@ -1359,6 +1420,22 @@ public function testExecutePartitionedUpdate() $this->assertEquals(1, $res); } + public function testExecutePartitionedUpdateWithIsolationLevelShouldRaise() + { + $sql = 'UPDATE foo SET bar = @bar'; + + $this->refreshOperation($this->database, $this->connection->reveal()); + $this->expectException(\InvalidArgumentException::class); + + $res = $this->database->executePartitionedUpdate($sql, [ + 'transactionOptions' => [ + 'isolationLevel' => IsolationLevel::REPEATABLE_READ + ] + ]); + + $this->assertEquals(1, $res); + } + public function testRead() { $table = 'Table'; @@ -2309,7 +2386,52 @@ public function testMutationKeyIsNotSetWhenTransactionIdExists() ]; $t = $this->database->transaction(); $t->insert(self::TEST_TABLE_NAME, $row); - $t->commit(); + $t->commit(); + } + + public function testRunTransactionIsolationLevel() + { + $gapic = $this->prophesize(SpannerClient::class); + + $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); + $session = new SessionProto(['name' => $sessName]); + $resultSet = new ResultSet(['stats' => new ResultSetStats(['row_count_exact' => 0])]); + $gapic->createSession(Argument::cetera())->shouldBeCalled()->willReturn($session); + $gapic->deleteSession(Argument::cetera())->shouldBeCalled(); + + $sql = 'SELECT example FROM sql_query'; + $stream = $this->prophesize(ServerStream::class); + $stream->readAll()->shouldBeCalledOnce()->willReturn([$resultSet]); + $gapic->executeStreamingSql($sessName, $sql, Argument::that(function (array $options) { + $this->assertArrayHasKey('transaction', $options); + $this->assertNotNull($transactionOptions = $options['transaction']->getBegin()); + $this->assertEquals(IsolationLevel::REPEATABLE_READ, $transactionOptions->getIsolationLevel()); + return true; + })) + ->shouldBeCalledOnce() + ->willReturn($stream->reveal()); + + $database = new Database( + new Grpc(['gapicSpannerClient' => $gapic->reveal()]), + $this->instance, + $this->lro->reveal(), + $this->lroCallables, + self::PROJECT, + self::DATABASE + ); + + $database->runTransaction( + function (Transaction $t) use ($sql) { + // Run a fake query + $t->executeUpdate($sql); + + // Simulate calling Transaction::commmit() + $prop = new \ReflectionProperty($t, 'state'); + $prop->setAccessible(true); + $prop->setValue($t, Transaction::STATE_COMMITTED); + }, + ['transactionOptions' => ['isolationLevel' => IsolationLevel::REPEATABLE_READ]] + ); } private function createStreamingAPIArgs() diff --git a/Spanner/tests/Unit/TransactionTypeTest.php b/Spanner/tests/Unit/TransactionTypeTest.php index 9fcd840c8ddf..726a00b08d5a 100644 --- a/Spanner/tests/Unit/TransactionTypeTest.php +++ b/Spanner/tests/Unit/TransactionTypeTest.php @@ -41,6 +41,7 @@ use Google\Cloud\Spanner\V1\RollbackRequest; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; use Google\Cloud\Spanner\V1\TransactionOptions; +use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\Cloud\Spanner\V1\TransactionOptions\PBReadOnly; use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite; use Google\Protobuf\Duration; From 29a187e9ad1818b4b87a0cf441acec65244541f4 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 1 Oct 2025 15:27:15 +0000 Subject: [PATCH 2/2] cleanup merge --- Spanner/src/Database.php | 4 +--- Spanner/src/Instance.php | 4 ++-- Spanner/tests/System/TransactionTest.php | 6 ------ Spanner/tests/Unit/TransactionTypeTest.php | 1 - 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 57ce9911ac04..db2a6246f1c6 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -193,7 +193,6 @@ public function __construct( $this->optionsValidator = new OptionsValidator($serializer); $this->directedReadOptions = $instance->directedReadOptions(); - $this->returnInt64AsObject = $returnInt64AsObject; $this->isolationLevel = $options['isolationLevel'] ?? IsolationLevel::ISOLATION_LEVEL_UNSPECIFIED; } @@ -929,7 +928,6 @@ public function runTransaction(callable $operation, array $options = []): mixed $maxRetries = $retrySettings instanceof RetrySettings ? $retrySettings->getMaxRetries() : $retrySettings['maxRetries']; - if (!isset($options['transactionOptions']['isolationLevel'])) { $options['transactionOptions']['isolationLevel'] = $this->isolationLevel; @@ -1933,8 +1931,8 @@ public function executePartitionedUpdate($statement, array $options = []): int $options['transactionOptions']['excludeTxnFromChangeStreams']; unset($options['transactionOptions']); } - $transaction = $this->operation->transaction($this->session, $beginTransactionOptions); + return $this->operation->executeUpdate($this->session, $transaction, $statement, [ 'statsItem' => 'rowCountLowerBound', 'route-to-leader' => true, diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index 1da5d6a0b9b1..1cdf17317155 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -560,8 +560,8 @@ public function database(string $name, array $options = []): Database $options + [ 'routeToLeader' => $this->routeToLeader, 'defaultQueryOptions' => $this->defaultQueryOptions, - 'returnInt64AsObject' => $this->returnInt64AsObject, - 'isolationLevel' => $this->isolationLevel, + 'returnInt64AsObject' => $this->returnInt64AsObject, + 'isolationLevel' => $this->isolationLevel, ] ); } diff --git a/Spanner/tests/System/TransactionTest.php b/Spanner/tests/System/TransactionTest.php index 54ca8607402f..cefeebf2e331 100644 --- a/Spanner/tests/System/TransactionTest.php +++ b/Spanner/tests/System/TransactionTest.php @@ -28,7 +28,6 @@ use ReflectionClass; use Google\Cloud\Spanner\V1\ReadRequest\LockHint; use Google\Cloud\Spanner\V1\ReadRequest\OrderBy; -use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; /** * @group spanner @@ -428,11 +427,6 @@ public function testRunTransactionILBWithMultipleOperations() 'name' => uniqid(self::TESTING_PREFIX), 'birthday' => new Date(new \DateTime()) ], - 'transaction' => [ - 'begin' => [ - 'isolationLevel' => IsolationLevel::REPEATABLE_READ, - ] - ] ] ); $transactionId = $t->id(); diff --git a/Spanner/tests/Unit/TransactionTypeTest.php b/Spanner/tests/Unit/TransactionTypeTest.php index 726a00b08d5a..9fcd840c8ddf 100644 --- a/Spanner/tests/Unit/TransactionTypeTest.php +++ b/Spanner/tests/Unit/TransactionTypeTest.php @@ -41,7 +41,6 @@ use Google\Cloud\Spanner\V1\RollbackRequest; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; use Google\Cloud\Spanner\V1\TransactionOptions; -use Google\Cloud\Spanner\V1\TransactionOptions\IsolationLevel; use Google\Cloud\Spanner\V1\TransactionOptions\PBReadOnly; use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite; use Google\Protobuf\Duration;