|
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 | use Google\Cloud\Spanner\V1\ReadRequest\LockHint; |
61 | 62 | use Google\Cloud\Spanner\V1\ReadRequest\OrderBy; |
62 | 63 |
|
@@ -94,7 +95,6 @@ class DatabaseTest extends TestCase |
94 | 95 | private $directedReadOptionsIncludeReplicas; |
95 | 96 | private $directedReadOptionsExcludeReplicas; |
96 | 97 |
|
97 | | - |
98 | 98 | public function setUp(): void |
99 | 99 | { |
100 | 100 | $this->checkAndSkipGrpcTests(); |
@@ -253,7 +253,7 @@ public function testBackups() |
253 | 253 | ] |
254 | 254 | ]; |
255 | 255 |
|
256 | | - $expectedFilter = "database:".$this->database->name(); |
| 256 | + $expectedFilter = 'database:' . $this->database->name(); |
257 | 257 | $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) |
258 | 258 | ->shouldBeCalled() |
259 | 259 | ->willReturn(['backups' => $backups]); |
@@ -281,8 +281,8 @@ public function testBackupsWithCustomFilter() |
281 | 281 | 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2'), |
282 | 282 | ] |
283 | 283 | ]; |
284 | | - $defaultFilter = "database:" . $this->database->name(); |
285 | | - $customFilter = "customFilter"; |
| 284 | + $defaultFilter = 'database:' . $this->database->name(); |
| 285 | + $customFilter = 'customFilter'; |
286 | 286 | $expectedFilter = sprintf('(%1$s) AND (%2$s)', $defaultFilter, $customFilter); |
287 | 287 |
|
288 | 288 | $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) |
@@ -412,7 +412,7 @@ public function testCreatePostgresDialect() |
412 | 412 | $this->database->___setProperty('connection', $this->connection->reveal()); |
413 | 413 |
|
414 | 414 | $op = $this->database->create([ |
415 | | - 'databaseDialect'=> DatabaseDialect::POSTGRESQL |
| 415 | + 'databaseDialect' => DatabaseDialect::POSTGRESQL |
416 | 416 | ]); |
417 | 417 |
|
418 | 418 | $this->assertInstanceOf(LongRunningOperation::class, $op); |
@@ -710,7 +710,6 @@ public function testBatchWrite() |
710 | 710 | Argument::withEntry('mutationGroups', [$expectedMutationGroup]) |
711 | 711 | ))->shouldBeCalled()->willReturn(['foo result']); |
712 | 712 |
|
713 | | - |
714 | 713 | $mutationGroups = [ |
715 | 714 | ($this->database->mutationGroup(false)) |
716 | 715 | ->insertOrUpdate( |
@@ -2193,6 +2192,90 @@ public function testBatchWriteWithExcludeTxnFromChangeStreams() |
2193 | 2192 | ]); |
2194 | 2193 | } |
2195 | 2194 |
|
| 2195 | + public function testRunTransactionWithReadLockMode() |
| 2196 | + { |
| 2197 | + $expectedReadLockMode = ReadLockMode::OPTIMISTIC; |
| 2198 | + |
| 2199 | + $gapic = $this->prophesize(SpannerClient::class); |
| 2200 | + |
| 2201 | + $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); |
| 2202 | + $session = new SessionProto(['name' => $sessName]); |
| 2203 | + $resultSet = new ResultSet(['stats' => new ResultSetStats(['row_count_exact' => 0])]); |
| 2204 | + $gapic->createSession(Argument::cetera())->shouldBeCalled()->willReturn($session); |
| 2205 | + $gapic->deleteSession(Argument::cetera())->shouldBeCalled(); |
| 2206 | + |
| 2207 | + $sql = 'SELECT example FROM sql_query'; |
| 2208 | + $stream = $this->prophesize(ServerStream::class); |
| 2209 | + $stream->readAll()->shouldBeCalledOnce()->willReturn([$resultSet]); |
| 2210 | + $gapic->executeStreamingSql( |
| 2211 | + $sessName, |
| 2212 | + $sql, |
| 2213 | + Argument::that(function (array $options) use ($expectedReadLockMode) { |
| 2214 | + $this->assertArrayHasKey('transaction', $options); |
| 2215 | + $this->assertNotNull($transactionOptions = $options['transaction']->getBegin()); |
| 2216 | + $this->assertNotNull($readWriteTxnOptions = $transactionOptions->getReadWrite()); |
| 2217 | + $this->assertNotNull($readLockModeOption = $readWriteTxnOptions->getReadLockMode()); |
| 2218 | + $this->assertEquals( |
| 2219 | + $expectedReadLockMode, |
| 2220 | + $readLockModeOption |
| 2221 | + ); |
| 2222 | + return true; |
| 2223 | + }) |
| 2224 | + ) |
| 2225 | + ->shouldBeCalledOnce() |
| 2226 | + ->willReturn($stream->reveal()); |
| 2227 | + |
| 2228 | + $database = new Database( |
| 2229 | + new Grpc(['gapicSpannerClient' => $gapic->reveal()]), |
| 2230 | + $this->instance, |
| 2231 | + $this->lro->reveal(), |
| 2232 | + $this->lroCallables, |
| 2233 | + self::PROJECT, |
| 2234 | + self::DATABASE |
| 2235 | + ); |
| 2236 | + |
| 2237 | + // Test TransactionOption array format with base level property set for readLockMode |
| 2238 | + // This helps test proper formating by the library to the format expected by Spanner backend |
| 2239 | + // (i.e. readLockMode should be inside readWrite) |
| 2240 | + $database->runTransaction( |
| 2241 | + function (Transaction $t) use ($sql) { |
| 2242 | + // Run a fake query |
| 2243 | + $t->executeUpdate($sql); |
| 2244 | + |
| 2245 | + // Simulate calling Transaction::commmit() |
| 2246 | + $prop = new \ReflectionProperty($t, 'state'); |
| 2247 | + $prop->setAccessible(true); |
| 2248 | + $prop->setValue($t, Transaction::STATE_COMMITTED); |
| 2249 | + }, |
| 2250 | + ['transactionOptions' => ['readLockMode' => $expectedReadLockMode, ] ] |
| 2251 | + ); |
| 2252 | + } |
| 2253 | + |
| 2254 | + public function testTransactionWithReadLockMode() |
| 2255 | + { |
| 2256 | + $expectedReadLockMode = ReadLockMode::OPTIMISTIC; |
| 2257 | + |
| 2258 | + $this->connection->beginTransaction( |
| 2259 | + Argument::that(function (array $args) use ($expectedReadLockMode) { |
| 2260 | + $this->assertArrayHasKey('transactionOptions', $args); |
| 2261 | + $this->assertArrayHasKey('readWrite', $args['transactionOptions']); |
| 2262 | + $this->assertArrayHasKey('readLockMode', $args['transactionOptions']['readWrite']); |
| 2263 | + $this->assertEquals( |
| 2264 | + $expectedReadLockMode, |
| 2265 | + $args['transactionOptions']['readWrite']['readLockMode'], |
| 2266 | + "The read lock mode received was {$args['transactionOptions']['readWrite']['readLockMode']} " . |
| 2267 | + "does not match expected {$expectedReadLockMode}" |
| 2268 | + ); |
| 2269 | + return true; |
| 2270 | + }) |
| 2271 | + ) |
| 2272 | + ->shouldBeCalled() |
| 2273 | + ->willReturn(['id' => self::TRANSACTION]); |
| 2274 | + |
| 2275 | + $t = $this->database->transaction(['transactionOptions' => ['readLockMode' => $expectedReadLockMode, ]]); |
| 2276 | + $this->assertInstanceOf(Transaction::class, $t); |
| 2277 | + } |
| 2278 | + |
2196 | 2279 | private function createStreamingAPIArgs() |
2197 | 2280 | { |
2198 | 2281 | $row = ['id' => 1]; |
|
0 commit comments