|
21 | 21 | use Google\Cloud\Core\Exception\AbortedException; |
22 | 22 | use Google\Cloud\Core\Exception\NotFoundException; |
23 | 23 | use Google\Cloud\Core\Exception\ServerException; |
| 24 | +use Google\Cloud\Core\Exception\ServiceException; |
24 | 25 | use Google\Cloud\Core\Iam\Iam; |
25 | 26 | use Google\Cloud\Core\Iterator\ItemIterator; |
26 | 27 | use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; |
|
45 | 46 | use Google\Cloud\Spanner\Tests\StubCreationTrait; |
46 | 47 | use Google\Cloud\Spanner\Timestamp; |
47 | 48 | use Google\Cloud\Spanner\Transaction; |
| 49 | +use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; |
48 | 50 | use Google\Cloud\Spanner\V1\ResultSet; |
49 | 51 | use Google\Cloud\Spanner\V1\ResultSetStats; |
50 | | -use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; |
51 | 52 | use Google\Cloud\Spanner\V1\Session as SessionProto; |
52 | 53 | use Google\Cloud\Spanner\V1\SpannerClient; |
53 | 54 | use Google\Cloud\Spanner\V1\Transaction as TransactionProto; |
54 | 55 | use Google\Cloud\Spanner\V1\TransactionOptions; |
| 56 | +use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite\ReadLockMode as ReadLockMode; |
55 | 57 | use Google\Rpc\Code; |
56 | 58 | use PHPUnit\Framework\TestCase; |
57 | 59 | use Prophecy\Argument; |
58 | 60 | use Prophecy\PhpUnit\ProphecyTrait; |
59 | | -use Google\Cloud\Core\Exception\ServiceException; |
60 | 61 |
|
61 | 62 | /** |
62 | 63 | * @group spanner |
@@ -92,7 +93,6 @@ class DatabaseTest extends TestCase |
92 | 93 | private $directedReadOptionsIncludeReplicas; |
93 | 94 | private $directedReadOptionsExcludeReplicas; |
94 | 95 |
|
95 | | - |
96 | 96 | public function setUp(): void |
97 | 97 | { |
98 | 98 | $this->checkAndSkipGrpcTests(); |
@@ -251,7 +251,7 @@ public function testBackups() |
251 | 251 | ] |
252 | 252 | ]; |
253 | 253 |
|
254 | | - $expectedFilter = "database:".$this->database->name(); |
| 254 | + $expectedFilter = 'database:' . $this->database->name(); |
255 | 255 | $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) |
256 | 256 | ->shouldBeCalled() |
257 | 257 | ->willReturn(['backups' => $backups]); |
@@ -279,8 +279,8 @@ public function testBackupsWithCustomFilter() |
279 | 279 | 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2'), |
280 | 280 | ] |
281 | 281 | ]; |
282 | | - $defaultFilter = "database:" . $this->database->name(); |
283 | | - $customFilter = "customFilter"; |
| 282 | + $defaultFilter = 'database:' . $this->database->name(); |
| 283 | + $customFilter = 'customFilter'; |
284 | 284 | $expectedFilter = sprintf('(%1$s) AND (%2$s)', $defaultFilter, $customFilter); |
285 | 285 |
|
286 | 286 | $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) |
@@ -410,7 +410,7 @@ public function testCreatePostgresDialect() |
410 | 410 | $this->database->___setProperty('connection', $this->connection->reveal()); |
411 | 411 |
|
412 | 412 | $op = $this->database->create([ |
413 | | - 'databaseDialect'=> DatabaseDialect::POSTGRESQL |
| 413 | + 'databaseDialect' => DatabaseDialect::POSTGRESQL |
414 | 414 | ]); |
415 | 415 |
|
416 | 416 | $this->assertInstanceOf(LongRunningOperation::class, $op); |
@@ -708,7 +708,6 @@ public function testBatchWrite() |
708 | 708 | Argument::withEntry('mutationGroups', [$expectedMutationGroup]) |
709 | 709 | ))->shouldBeCalled()->willReturn(['foo result']); |
710 | 710 |
|
711 | | - |
712 | 711 | $mutationGroups = [ |
713 | 712 | ($this->database->mutationGroup(false)) |
714 | 713 | ->insertOrUpdate( |
@@ -2139,6 +2138,90 @@ public function testBatchWriteWithExcludeTxnFromChangeStreams() |
2139 | 2138 | ]); |
2140 | 2139 | } |
2141 | 2140 |
|
| 2141 | + public function testRunTransactionWithReadLockMode() |
| 2142 | + { |
| 2143 | + $expectedReadLockMode = ReadLockMode::OPTIMISTIC; |
| 2144 | + |
| 2145 | + $gapic = $this->prophesize(SpannerClient::class); |
| 2146 | + |
| 2147 | + $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); |
| 2148 | + $session = new SessionProto(['name' => $sessName]); |
| 2149 | + $resultSet = new ResultSet(['stats' => new ResultSetStats(['row_count_exact' => 0])]); |
| 2150 | + $gapic->createSession(Argument::cetera())->shouldBeCalled()->willReturn($session); |
| 2151 | + $gapic->deleteSession(Argument::cetera())->shouldBeCalled(); |
| 2152 | + |
| 2153 | + $sql = 'SELECT example FROM sql_query'; |
| 2154 | + $stream = $this->prophesize(ServerStream::class); |
| 2155 | + $stream->readAll()->shouldBeCalledOnce()->willReturn([$resultSet]); |
| 2156 | + $gapic->executeStreamingSql( |
| 2157 | + $sessName, |
| 2158 | + $sql, |
| 2159 | + Argument::that(function (array $options) use ($expectedReadLockMode) { |
| 2160 | + $this->assertArrayHasKey('transaction', $options); |
| 2161 | + $this->assertNotNull($transactionOptions = $options['transaction']->getBegin()); |
| 2162 | + $this->assertNotNull($readWriteTxnOptions = $transactionOptions->getReadWrite()); |
| 2163 | + $this->assertNotNull($readLockModeOption = $readWriteTxnOptions->getReadLockMode()); |
| 2164 | + $this->assertEquals( |
| 2165 | + $expectedReadLockMode, |
| 2166 | + $readLockModeOption |
| 2167 | + ); |
| 2168 | + return true; |
| 2169 | + }) |
| 2170 | + ) |
| 2171 | + ->shouldBeCalledOnce() |
| 2172 | + ->willReturn($stream->reveal()); |
| 2173 | + |
| 2174 | + $database = new Database( |
| 2175 | + new Grpc(['gapicSpannerClient' => $gapic->reveal()]), |
| 2176 | + $this->instance, |
| 2177 | + $this->lro->reveal(), |
| 2178 | + $this->lroCallables, |
| 2179 | + self::PROJECT, |
| 2180 | + self::DATABASE |
| 2181 | + ); |
| 2182 | + |
| 2183 | + // Test TransactionOption array format with base level property set for readLockMode |
| 2184 | + // This helps test proper formating by the library to the format expected by Spanner backend |
| 2185 | + // (i.e. readLockMode should be inside readWrite) |
| 2186 | + $database->runTransaction( |
| 2187 | + function (Transaction $t) use ($sql) { |
| 2188 | + // Run a fake query |
| 2189 | + $t->executeUpdate($sql); |
| 2190 | + |
| 2191 | + // Simulate calling Transaction::commmit() |
| 2192 | + $prop = new \ReflectionProperty($t, 'state'); |
| 2193 | + $prop->setAccessible(true); |
| 2194 | + $prop->setValue($t, Transaction::STATE_COMMITTED); |
| 2195 | + }, |
| 2196 | + ['transactionOptions' => ['readLockMode' => $expectedReadLockMode, ] ] |
| 2197 | + ); |
| 2198 | + } |
| 2199 | + |
| 2200 | + public function testTransactionWithReadLockMode() |
| 2201 | + { |
| 2202 | + $expectedReadLockMode = ReadLockMode::OPTIMISTIC; |
| 2203 | + |
| 2204 | + $this->connection->beginTransaction( |
| 2205 | + Argument::that(function (array $args) use ($expectedReadLockMode) { |
| 2206 | + $this->assertArrayHasKey('transactionOptions', $args); |
| 2207 | + $this->assertArrayHasKey('readWrite', $args['transactionOptions']); |
| 2208 | + $this->assertArrayHasKey('readLockMode', $args['transactionOptions']['readWrite']); |
| 2209 | + $this->assertEquals( |
| 2210 | + $expectedReadLockMode, |
| 2211 | + $args['transactionOptions']['readWrite']['readLockMode'], |
| 2212 | + "The read lock mode received was {$args['transactionOptions']['readWrite']['readLockMode']} " . |
| 2213 | + "does not match expected {$expectedReadLockMode}" |
| 2214 | + ); |
| 2215 | + return true; |
| 2216 | + }) |
| 2217 | + ) |
| 2218 | + ->shouldBeCalled() |
| 2219 | + ->willReturn(['id' => self::TRANSACTION]); |
| 2220 | + |
| 2221 | + $t = $this->database->transaction(['transactionOptions' => ['readLockMode' => $expectedReadLockMode, ]]); |
| 2222 | + $this->assertInstanceOf(Transaction::class, $t); |
| 2223 | + } |
| 2224 | + |
2142 | 2225 | private function createStreamingAPIArgs() |
2143 | 2226 | { |
2144 | 2227 | $row = ['id' => 1]; |
|
0 commit comments