From c61770146e8b4ee20f8cb1a2f81ccf8106104b99 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Fri, 15 Nov 2024 16:52:19 -0800 Subject: [PATCH 01/12] feat!: Spanner V2 --- Core/src/ApiHelperTrait.php | 4 +- Core/src/Iam/Iam.php | 2 +- Core/src/Middleware/ExceptionMiddleware.php | 86 + Core/src/RequestHandler.php | 15 +- Core/src/RequestProcessorTrait.php | 5 +- Core/src/ServiceBuilder.php | 2 +- Core/tests/Snippet/Iam/IamTest.php | 3 +- .../OperationResponseTraitTest.php | 4 +- Core/tests/Unit/ServiceBuilderTest.php | 6 - Spanner/MIGRATING.md | 115 + Spanner/composer.json | 19 +- Spanner/phpunit.xml.dist | 2 +- Spanner/src/ArrayType.php | 2 +- Spanner/src/Backup.php | 296 ++- Spanner/src/Batch/BatchClient.php | 4 +- Spanner/src/Batch/BatchSnapshot.php | 2 +- Spanner/src/Batch/QueryPartition.php | 4 +- Spanner/src/Batch/ReadPartition.php | 2 +- Spanner/src/BatchDmlResult.php | 2 +- Spanner/src/Bytes.php | 2 +- Spanner/src/CommitTimestamp.php | 2 +- .../src/Connection/ConnectionInterface.php | 291 --- Spanner/src/Connection/Grpc.php | 1775 ------------- Spanner/src/Connection/IamDatabase.php | 65 - Spanner/src/Connection/IamInstance.php | 65 - .../src/Connection/LongRunningConnection.php | 75 - Spanner/src/Database.php | 782 ++++-- Spanner/src/Date.php | 2 +- Spanner/src/Duration.php | 121 - Spanner/src/Instance.php | 493 ++-- Spanner/src/InstanceConfiguration.php | 272 +- Spanner/src/KeyRange.php | 2 +- Spanner/src/KeySet.php | 3 +- Spanner/src/MutationTrait.php | 7 +- Spanner/src/Numeric.php | 2 +- Spanner/src/Operation.php | 501 +++- Spanner/src/PgJsonb.php | 2 +- Spanner/src/PgNumeric.php | 2 +- Spanner/src/PgOid.php | 2 +- Spanner/src/RequestHeaderTrait.php | 84 - Spanner/src/RequestTrait.php | 127 + Spanner/src/Result.php | 17 +- Spanner/src/Serializer.php | 99 + Spanner/src/Session/CacheSessionPool.php | 23 +- Spanner/src/Session/Session.php | 98 +- Spanner/src/Snapshot.php | 5 +- Spanner/src/SnapshotTrait.php | 9 +- Spanner/src/SpannerClient.php | 399 +-- Spanner/src/StructType.php | 2 +- Spanner/src/StructValue.php | 2 +- Spanner/src/Timestamp.php | 2 +- Spanner/src/Transaction.php | 40 +- Spanner/src/TransactionConfigurationTrait.php | 24 +- Spanner/src/TransactionalReadTrait.php | 37 +- Spanner/src/ValueMapper.php | 15 +- Spanner/test.php | 36 + Spanner/tests/OperationRefreshTrait.php | 41 - Spanner/tests/Perf/ycsb.php | 2 +- Spanner/tests/ResultGeneratorTrait.php | 140 +- Spanner/tests/Snippet/ArrayTypeTest.php | 115 +- Spanner/tests/Snippet/BackupTest.php | 217 +- .../tests/Snippet/Batch/BatchClientTest.php | 123 +- .../tests/Snippet/Batch/BatchSnapshotTest.php | 71 +- .../Batch/PartitionSharedSnippetTestTrait.php | 2 +- .../Snippet/Batch/QueryPartitionTest.php | 36 +- .../tests/Snippet/Batch/ReadPartitionTest.php | 38 +- Spanner/tests/Snippet/BatchDmlResultTest.php | 45 +- Spanner/tests/Snippet/BytesTest.php | 4 +- Spanner/tests/Snippet/CommitTimestampTest.php | 56 +- Spanner/tests/Snippet/DatabaseTest.php | 667 ++--- Spanner/tests/Snippet/DateTest.php | 4 +- Spanner/tests/Snippet/DurationTest.php | 85 - .../Snippet/InstanceConfigurationTest.php | 101 +- Spanner/tests/Snippet/InstanceTest.php | 291 ++- Spanner/tests/Snippet/KeyRangeTest.php | 4 +- Spanner/tests/Snippet/KeySetTest.php | 2 +- Spanner/tests/Snippet/NumericTest.php | 1 - Spanner/tests/Snippet/ResultTest.php | 2 +- .../Snippet/Session/CacheSessionPoolTest.php | 6 +- Spanner/tests/Snippet/SnapshotTest.php | 11 +- Spanner/tests/Snippet/SpannerClientTest.php | 68 +- Spanner/tests/Snippet/StructTypeTest.php | 64 +- Spanner/tests/Snippet/StructValueTest.php | 79 +- Spanner/tests/Snippet/TimestampTest.php | 6 +- Spanner/tests/Snippet/TransactionTest.php | 229 +- .../Snippet/TransactionalReadMethodsTest.php | 287 ++- Spanner/tests/StubCreationTrait.php | 38 - Spanner/tests/System/AdminTest.php | 20 +- Spanner/tests/System/BackupTest.php | 73 +- Spanner/tests/System/BatchTest.php | 61 +- Spanner/tests/System/BatchWriteTest.php | 6 +- .../System/GeneratedAdminEmulatorTest.php | 2 +- Spanner/tests/System/LargeReadTest.php | 8 +- Spanner/tests/System/OperationsTest.php | 8 +- Spanner/tests/System/PartitionedDmlTest.php | 2 - Spanner/tests/System/PgBatchTest.php | 6 +- Spanner/tests/System/PgBatchWriteTest.php | 6 +- Spanner/tests/System/PgPartitionedDmlTest.php | 2 - Spanner/tests/System/PgQueryTest.php | 22 +- Spanner/tests/System/PgReadTest.php | 8 +- Spanner/tests/System/PgTransactionTest.php | 2 +- Spanner/tests/System/PgWriteTest.php | 59 +- Spanner/tests/System/QueryTest.php | 74 +- Spanner/tests/System/ReadTest.php | 4 +- Spanner/tests/System/SessionTest.php | 4 +- Spanner/tests/System/SnapshotTest.php | 16 +- Spanner/tests/System/SpannerPgTestCase.php | 10 +- Spanner/tests/System/SpannerTestCase.php | 12 +- Spanner/tests/System/TransactionTest.php | 9 +- Spanner/tests/System/WriteTest.php | 55 +- .../System/pcntl/AbortedErrorCausesRetry.php | 2 +- ...tTransactionsIncrementValueWithExecute.php | 4 +- ...rentTransactionsIncrementValueWithRead.php | 6 +- .../V1/Client/DatabaseAdminClientTest.php | 4 +- .../V1/Client/InstanceAdminClientTest.php | 4 +- Spanner/tests/Unit/ArrayTypeTest.php | 2 +- Spanner/tests/Unit/BackupTest.php | 374 ++- Spanner/tests/Unit/Batch/BatchClientTest.php | 142 +- .../tests/Unit/Batch/BatchSnapshotTest.php | 217 +- Spanner/tests/Unit/BytesTest.php | 2 +- Spanner/tests/Unit/CommitTimestampTest.php | 2 +- Spanner/tests/Unit/Connection/GrpcTest.php | 1690 ------------- .../tests/Unit/Connection/IamDatabaseTest.php | 69 - .../tests/Unit/Connection/IamInstanceTest.php | 69 - .../Connection/LongRunningConnectionTest.php | 70 - Spanner/tests/Unit/DatabaseTest.php | 2231 +++++++++-------- Spanner/tests/Unit/DateTest.php | 4 +- Spanner/tests/Unit/DurationTest.php | 66 - Spanner/tests/Unit/Fixtures.php | 5 - .../Unit/GapicBackoff/GapicBackoffTest.php | 114 - .../GapicBackoff/MockOperationResponse.php | 31 - .../tests/Unit/InstanceConfigurationTest.php | 290 ++- Spanner/tests/Unit/InstanceTest.php | 766 +++--- Spanner/tests/Unit/KeyRangeTest.php | 4 +- Spanner/tests/Unit/KeySetTest.php | 18 +- Spanner/tests/Unit/OperationTest.php | 512 ++-- Spanner/tests/Unit/PgJsonbTest.php | 4 +- Spanner/tests/Unit/PgNumericTest.php | 2 +- Spanner/tests/Unit/ResultTest.php | 259 +- Spanner/tests/Unit/ResultTestTrait.php | 112 - .../Unit/Session/CacheSessionPoolTest.php | 101 +- Spanner/tests/Unit/SnapshotTest.php | 8 +- Spanner/tests/Unit/SpannerClientTest.php | 373 +-- Spanner/tests/Unit/StructTypeTest.php | 14 +- Spanner/tests/Unit/StructValueTest.php | 4 +- Spanner/tests/Unit/TimestampTest.php | 4 +- .../TransactionConfigurationTraitTest.php | 16 +- Spanner/tests/Unit/TransactionTest.php | 688 ++--- Spanner/tests/Unit/TransactionTypeTest.php | 873 ++++--- .../V1/SpannerClientPartialVeneerTest.php | 45 - Spanner/tests/Unit/V1/SpannerClientTest.php | 1161 --------- Spanner/tests/Unit/ValueMapperTest.php | 32 +- Spanner/tests/Unit/bootstrap.php | 12 + Spanner/tests/Unit/fixtures/instance.json | 7 - composer.json | 6 +- dev/composer.json | 1 + 156 files changed, 8205 insertions(+), 11579 deletions(-) create mode 100644 Core/src/Middleware/ExceptionMiddleware.php create mode 100644 Spanner/MIGRATING.md delete mode 100644 Spanner/src/Connection/ConnectionInterface.php delete mode 100644 Spanner/src/Connection/Grpc.php delete mode 100644 Spanner/src/Connection/IamDatabase.php delete mode 100644 Spanner/src/Connection/IamInstance.php delete mode 100644 Spanner/src/Connection/LongRunningConnection.php delete mode 100644 Spanner/src/Duration.php delete mode 100644 Spanner/src/RequestHeaderTrait.php create mode 100644 Spanner/src/RequestTrait.php create mode 100644 Spanner/src/Serializer.php create mode 100644 Spanner/test.php delete mode 100644 Spanner/tests/OperationRefreshTrait.php delete mode 100644 Spanner/tests/Snippet/DurationTest.php delete mode 100644 Spanner/tests/StubCreationTrait.php delete mode 100644 Spanner/tests/Unit/Connection/GrpcTest.php delete mode 100644 Spanner/tests/Unit/Connection/IamDatabaseTest.php delete mode 100644 Spanner/tests/Unit/Connection/IamInstanceTest.php delete mode 100644 Spanner/tests/Unit/Connection/LongRunningConnectionTest.php delete mode 100644 Spanner/tests/Unit/DurationTest.php delete mode 100644 Spanner/tests/Unit/GapicBackoff/GapicBackoffTest.php delete mode 100644 Spanner/tests/Unit/GapicBackoff/MockOperationResponse.php delete mode 100644 Spanner/tests/Unit/ResultTestTrait.php delete mode 100644 Spanner/tests/Unit/V1/SpannerClientPartialVeneerTest.php delete mode 100644 Spanner/tests/Unit/V1/SpannerClientTest.php create mode 100644 Spanner/tests/Unit/bootstrap.php delete mode 100644 Spanner/tests/Unit/fixtures/instance.json diff --git a/Core/src/ApiHelperTrait.php b/Core/src/ApiHelperTrait.php index d9900ea22944..8bc47049fef0 100644 --- a/Core/src/ApiHelperTrait.php +++ b/Core/src/ApiHelperTrait.php @@ -261,8 +261,8 @@ private function splitOptionalArgs(array $input, array $extraAllowedKeys = []) : $callOptionFields = array_keys((new CallOptions([]))->toArray()); $keys = array_merge($callOptionFields, $extraAllowedKeys); - $optionalArgs = $this->pluckArray($keys, $input); + $callOptions = $this->pluckArray($keys, $input); - return [$input, $optionalArgs]; + return [$input, $callOptions]; } } diff --git a/Core/src/Iam/Iam.php b/Core/src/Iam/Iam.php index 3d530d8a3fc2..cd351934da6d 100644 --- a/Core/src/Iam/Iam.php +++ b/Core/src/Iam/Iam.php @@ -34,7 +34,7 @@ * * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $instance = $spanner->instance('my-new-instance'); * * $iam = $instance->iam(); diff --git a/Core/src/Middleware/ExceptionMiddleware.php b/Core/src/Middleware/ExceptionMiddleware.php new file mode 100644 index 000000000000..d99a253db2fb --- /dev/null +++ b/Core/src/Middleware/ExceptionMiddleware.php @@ -0,0 +1,86 @@ +nextHandler = $nextHandler; + } + + /** + * @param Call $call + * @param array $options + * + * @return PromiseInterface|ClientStream|ServerStream|BidiStream + */ + public function __invoke(Call $call, array $options) + { + $response = ($this->nextHandler)($call, $options); + if ($response instanceof PromiseInterface) { + return $response->then(null, function ($value) { + if ($value instanceof \Google\ApiCore\ApiException) { + throw $this->convertToGoogleException($value); + } + if ($value instanceof Throwable) { + throw $value; + } + }); + } + // this can also be a Stream + return $response; + } +} + diff --git a/Core/src/RequestHandler.php b/Core/src/RequestHandler.php index 6e11b4d61e33..7fff6befc8eb 100644 --- a/Core/src/RequestHandler.php +++ b/Core/src/RequestHandler.php @@ -45,11 +45,11 @@ class RequestHandler */ private Serializer $serializer; - private array $clients; + private array $clients = []; /** * @param Serializer $serializer - * @param array $clientClasses + * @param array $clientClasses * @param array $clientConfig */ public function __construct( @@ -76,11 +76,14 @@ public function __construct( ); } //@codeCoverageIgnoreEnd - + // Initialize the client classes and store them in memory - $this->clients = []; - foreach ($clientClasses as $className) { - $this->clients[$className] = new $className($clientConfig); + foreach ($clientClasses as $client) { + if (is_object($client)) { + $this->clients[get_class($client)] = $client; + } else { + $this->clients[$client] = new $client($clientConfig); + } } } diff --git a/Core/src/RequestProcessorTrait.php b/Core/src/RequestProcessorTrait.php index 72440cd9194f..2f0dd1f41465 100644 --- a/Core/src/RequestProcessorTrait.php +++ b/Core/src/RequestProcessorTrait.php @@ -25,6 +25,7 @@ use \Google\Protobuf\Internal\Message; use Google\Rpc\RetryInfo; use Google\Rpc\BadRequest; +use GuzzleHttp\Promise\PromiseInterface; /** * @internal @@ -45,7 +46,7 @@ trait RequestProcessorTrait * Serializes a gRPC response. * * @param mixed $response - * @return \Generator|OperationResponse|array|null + * @return \Generator|OperationResponse|array|PromiseInterface|null */ private function handleResponse($response) { @@ -57,7 +58,7 @@ private function handleResponse($response) return $this->serializer->encodeMessage($response); } - if ($response instanceof OperationResponse) { + if ($response instanceof OperationResponse || $response instanceof PromiseInterface) { return $response; } diff --git a/Core/src/ServiceBuilder.php b/Core/src/ServiceBuilder.php index 9a5ac3f4f822..786d74571163 100644 --- a/Core/src/ServiceBuilder.php +++ b/Core/src/ServiceBuilder.php @@ -249,7 +249,7 @@ public function pubsub(array $config = []) * * Example: * ``` - * $spanner = $cloud->spanner(); + * $spanner = $cloud->spanner(['projectId' => 'my-project']); * ``` * * @param array $config [optional] { diff --git a/Core/tests/Snippet/Iam/IamTest.php b/Core/tests/Snippet/Iam/IamTest.php index 4c9ae4988b61..24283448fe3a 100644 --- a/Core/tests/Snippet/Iam/IamTest.php +++ b/Core/tests/Snippet/Iam/IamTest.php @@ -19,6 +19,7 @@ use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Iam\Iam; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iam\IamConnectionInterface; use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\SpannerClient; @@ -55,7 +56,7 @@ public function testClass() ]); $res = $snippet->invoke('iam'); - $this->assertInstanceOf(Iam::class, $res->returnVal()); + $this->assertInstanceOf(IamManager::class, $res->returnVal()); } public function testPolicy() diff --git a/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php b/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php index 89c35f11752f..dcd57f9cbdc8 100644 --- a/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php +++ b/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php @@ -18,10 +18,12 @@ namespace Google\Cloud\Core\Tests\Unit\LongRunning; use Google\ApiCore\OperationResponse; +use Google\ApiCore\Serializer; use Google\Cloud\Core\LongRunning\OperationResponseTrait; use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\ApiCore\Serializer; +use Google\Cloud\Core\RequestHandler; +use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; use Prophecy\Argument; use Google\Cloud\Audit\RequestMetadata; use Google\Cloud\Audit\AuthorizationInfo; diff --git a/Core/tests/Unit/ServiceBuilderTest.php b/Core/tests/Unit/ServiceBuilderTest.php index 3667628bb299..33ce724a6bd2 100644 --- a/Core/tests/Unit/ServiceBuilderTest.php +++ b/Core/tests/Unit/ServiceBuilderTest.php @@ -24,7 +24,6 @@ use Google\Cloud\Firestore\FirestoreClient; use Google\Cloud\Language\LanguageClient; use Google\Cloud\Logging\LoggingClient; -use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Speech\SpeechClient; use Google\Cloud\Storage\StorageClient; use Google\Cloud\Core\Tests\Unit\Fixtures; @@ -175,11 +174,6 @@ public function serviceProvider() ], [ 'language', LanguageClient::class - ], [ - 'spanner', - SpannerClient::class, - [], - [$this, 'checkAndSkipGrpcTests'] ], [ 'speech', SpeechClient::class, diff --git a/Spanner/MIGRATING.md b/Spanner/MIGRATING.md new file mode 100644 index 000000000000..ecc9dc7bfaf6 --- /dev/null +++ b/Spanner/MIGRATING.md @@ -0,0 +1,115 @@ +# Migrating Google Spanner from V1 to V2 + +## How to upgrade + +Update your `google/cloud-spanner` dependency to `^2.0`: + +``` +{ + "require": { + "google/cloud-spanner": "^2.0" + } +} +``` + +## Changes + +### Client Options changes + +The following client options are removed/replaced with other options present in +[`ClientOptions`][ClientOptions]. This was done to ensure client options are consistent across all +Google Cloud clients. + +- `authCache` -> Moved to `credentialsConfig.authCache` +- `authCacheOptions` -> Moved to `credentialsConfig.authCacheOptions` +- `FetchAuthTokenInterface` -> Moved to `credentials` +- `keyFile` -> Moved to `credentials` +- `keyFilePath` -> Moved to `credentials` +- `requestTimeout` -> Removed from client options and moved to a call option `timeoutMillis` +- `scopes` -> Moved to `credentialsConfig.scopes` +- `quotaProject` -> Moved to `credentialsConfig.quotaProject` +- `httpHandler` -> Moved to `transportConfig.rest.httpHandler` +- `authHttpHandler` -> Moved to `credentialsConfig.authHttpHandler` +- `retries` -> Removed from client options and moved to call options `retrySettings.maxRetries` + +### Retry Options changes + +The retry options have been moved to use [`RetrySettings`][RetrySettings] in call options +and function parameters. + +- `retries` -> Renamed to `retrySettings.maxRetries` +- `maxRetries` -> Renamed to `retrySettings.maxRetries` + +[RetrySettings]: https://googleapis.github.io/gax-php/v1.26.1/Google/ApiCore/RetrySettings.html + +[ClientOptions]: https://googleapis.github.io/gax-php/v1.26.1/Google/ApiCore/Options/ClientOptions.html + +### Connection classes are not used anymore. + +This is a major change with this major version but one that we hope won't break any users. When the +`SpannerClient` was created, behind the scenes a connection adapter was initialized. +This connection object was then forwarded to any resource classes internally, +like so: + +```php +// This initialized a connection object +$client = new SpannerClient(); +// This passed on the connection object to the Instance class +$instance = $spanner->instance('my-instance'); +``` + +As you can see the connection object was handled internally. If you used the library in this way, +you will not need to make any changes. However, if you created the connection classes directly +and passed it to the `Instance` class, this will break in Spanner `v2`: + +```php +// Not intended +$connObj = new Grpc([]); +$instance = new Instance( + $connObj, + // other constructor options +); +``` + +### `Google\Cloud\Spanner\Duration` class is not used anymore. +We have removed the `Google\Cloud\Spanner\Duration` class from the library. Instead we will be using the `Google\Protobuf\Duration` class. + +### IAM class changes + +We have kept the functionality of `IAM` the same, however the underlying `IAM` class has changed. +```php +// In V1, this used to return an instance of Google\Cloud\Core\Iam\Iam +$iam = $instance->iam(); + +// In V2, this will return an instance of Google\Cloud\Core\Iam\IamManager +$iam = $instance->iam(); + +// Both the classes share the same functionality, so the following methods will work for both versions. +$iam->policy(); +$iam->setPolicy(); +$iam->testIamPermissions(); +``` + +### LongRunningOperation class changes + +We have kept the functionality of `LongRunningOperation` the same, +however the underlying `LongRunningOperation` class has changed. +```php +// In V1, this used to return an instance of Google\Cloud\Core\LongRunning\LongRunningOperation. +$lro = $instance->create($configuration); + +// In V2, this will return an instance of Google\ApiCore\OperationResponse. +$lro = $instance->create($configuration); + +// Both the classes share the same functionality, so the following methods will work for both versions. +$lro->name(); +$lro->done(); +$lro->state(); +$lro->result(); +$lro->error(); +$lro->info(); +$lro->reload(); +$lro->pollUntilComplete(); +$lro->cancel(); +$lro->delete(); +``` diff --git a/Spanner/composer.json b/Spanner/composer.json index 6bd4c5577770..8e3b38a20de8 100644 --- a/Spanner/composer.json +++ b/Spanner/composer.json @@ -6,8 +6,8 @@ "require": { "php": "^8.0", "ext-grpc": "*", - "google/cloud-core": "^1.52.7", - "google/gax": "^1.34.0" + "google/cloud-core": "1.60", + "google/gax": "dev-result-function as 1.40.0" }, "require-dev": { "phpunit/phpunit": "^9.0", @@ -16,7 +16,9 @@ "phpdocumentor/reflection": "^5.3.3", "phpdocumentor/reflection-docblock": "^5.3", "erusev/parsedown": "^1.6", - "google/cloud-pubsub": "^2.0" + "google/cloud-pubsub": "^2.0", + "dg/bypass-finals": "^1.7", + "dms/phpunit-arraysubset-asserts": "^0.5.0" }, "suggest": { "ext-protobuf": "Provides a significant increase in throughput over the pure PHP protobuf implementation. See https://cloud.google.com/php/grpc for installation instructions.", @@ -40,5 +42,16 @@ "psr-4": { "Google\\Cloud\\Spanner\\Tests\\": "tests" } + }, + "repositories": { + "google-cloud": { + "type": "path", + "url": "../Core", + "options": { + "versions": { + "google/cloud-core": "1.60" + } + } + } } } diff --git a/Spanner/phpunit.xml.dist b/Spanner/phpunit.xml.dist index 9923818f349f..33fc08b07b98 100644 --- a/Spanner/phpunit.xml.dist +++ b/Spanner/phpunit.xml.dist @@ -1,5 +1,5 @@ - + src diff --git a/Spanner/src/ArrayType.php b/Spanner/src/ArrayType.php index 518db7088370..db3aec8bcf4a 100644 --- a/Spanner/src/ArrayType.php +++ b/Spanner/src/ArrayType.php @@ -29,7 +29,7 @@ * use Google\Cloud\Spanner\Database; * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $database = $spanner->connect('my-instance', 'my-database'); * * $arrayType = new ArrayType(Database::TYPE_STRING); diff --git a/Spanner/src/Backup.php b/Spanner/src/Backup.php index 8f54a6297ba9..f050e4f42f08 100644 --- a/Spanner/src/Backup.php +++ b/Spanner/src/Backup.php @@ -17,16 +17,24 @@ namespace Google\Cloud\Spanner; +use Closure; +use DateTimeInterface; +use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\Iterator\ItemIterator; +use Google\Cloud\Core\Iterator\PageIterator; use Google\ApiCore\ValidationException; -use Google\Cloud\Core\ArrayTrait; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; +use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Backup\State; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; -use Google\Cloud\Core\LongRunning\LROTrait; -use DateTimeInterface; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\DeleteBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateBackupRequest; +use Google\LongRunning\ListOperationsRequest; /** * Represents a Cloud Spanner Backup. @@ -35,110 +43,39 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $backup = $spanner->instance('my-instance')->backup('my-backup'); * ``` - * - * @method resumeOperation() { - * Resume a long running operation - * - * Example: - * ``` - * $operation = $backup->resumeOperation($operationName); - * ``` - * - * @param string $operationName The long running operation name. - * @param array $info [optional] The operation data. - * @return LongRunningOperation - * } - * @method longRunningOperations() { - * List long running operations. - * - * Example: - * ``` - * $operations = $backup->longRunningOperations(); - * ``` - * - * @param array $options [optional] { - * Configuration Options. - * - * @type string $name The name of the operation collection. - * @type string $filter The standard list filter. - * @type int $pageSize Maximum number of results to return per - * request. - * @type int $resultLimit Limit the number of results returned in total. - * **Defaults to** `0` (return all results). - * @type string $pageToken A previously-returned page token used to - * resume the loading of results from a specific point. - * } - * @return ItemIterator - * } */ class Backup { - use ArrayTrait; - use LROTrait; + use RequestTrait; const STATE_READY = State::READY; const STATE_CREATING = State::CREATING; - /** - * @var ConnectionInterface - * @internal - */ - private $connection; - - /** - * @var Instance - */ - private $instance; - - /** - * @var string - */ - private $projectId; - - /** - * @var string - */ - private $name; - - /** - * @var array - */ - private $info; - /** * Create an object representing a Backup. * - * @param ConnectionInterface $connection The connection to the - * Cloud Spanner Admin API. This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @internal Backup is constructed by the {@see Instance} class. + * + * @param DatabaseAdminClient The database admin client to make backup RPC calls. + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param Instance $instance The instance in which the backup exists. - * @param LongRunningConnectionInterface $lroConnection An implementation - * mapping to methods which handle LRO resolution in the service. - * @param array $lroCallables * @param string $projectId The project ID. * @param string $name The backup name or ID. * @param array $info [optional] An array representing the backup resource. */ public function __construct( - ConnectionInterface $connection, - Instance $instance, - LongRunningConnectionInterface $lroConnection, - array $lroCallables, - $projectId, - $name, - array $info = [] + private DatabaseAdminClient $databaseAdminClient, + private Serializer $serializer, + private Instance $instance, + private string $projectId, + private string $name, + private array $info = [] ) { - $this->connection = $connection; - $this->instance = $instance; - $this->projectId = $projectId; $this->name = $this->fullyQualifiedBackupName($name); - $this->info = $info; - - $this->setLroProperties($lroConnection, $lroCallables, $this->name); } /** @@ -161,32 +98,32 @@ public function __construct( * consistent copy of the database. If not present, it will be the same * as the create time of the backup. * } - * @return LongRunningOperation + * @return OperationResponse * @throws \InvalidArgumentException */ public function create($database, DateTimeInterface $expireTime, array $options = []) { - if (isset($options['versionTime'])) { - if (!($options['versionTime'] instanceof DateTimeInterface)) { - throw new \InvalidArgumentException( - 'Optional argument `versionTime` must be a DateTimeInterface, got ' . - (is_object($options['versionTime']) - ? get_class($options['versionTime']) - : gettype($options['versionTime'])) - ); - } - $options['versionTime'] = $options['versionTime']->format('Y-m-d\TH:i:s.u\Z'); - } - $operation = $this->connection->createBackup([ - 'instance' => $this->instance->name(), + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data = $this->validateAndFormatVersionTime($data); + + $data += [ + 'parent' => $this->instance->name(), 'backupId' => DatabaseAdminClient::parseName($this->name)['backup'], 'backup' => [ 'database' => $this->instance->database($database)->name(), - 'expireTime' => $expireTime->format('Y-m-d\TH:i:s.u\Z'), + 'expireTime' => $this->formatTimestampForApi($expireTime->format('Y-m-d\TH:i:s.u\Z')) ], - ] + $options); + ]; + if (isset($data['versionTime'])) { + $data['backup']['versionTime'] = $data['versionTime']; + unset($data['versionTime']); + } + + $request = $this->serializer->decodeMessage(new CreateBackupRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); - return $this->resumeOperation($operation['name'], $operation); + return $this->databaseAdminClient->createBackup($request, $callOptions) + ->withResultFunction($this->backupResultFunction()); } /** @@ -209,24 +146,25 @@ public function create($database, DateTimeInterface $expireTime, array $options * eligible to be automatically deleted by Cloud Spanner. * @param array $options [optional] { * Configuration Options. - * - * @type DateTimeInterface $versionTime The version time for the externally - * consistent copy of the database. If not present, it will be the same - * as the create time of the backup. * } - * @return LongRunningOperation + * @return OperationResponse * @throws \InvalidArgumentException */ public function createCopy(Backup $newBackup, DateTimeInterface $expireTime, array $options = []) { - $operation = $this->connection->copyBackup([ - 'instance' => $newBackup->instance->name(), + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'parent' => $newBackup->instance->name(), 'backupId' => DatabaseAdminClient::parseName($newBackup->name)['backup'], - 'sourceBackupId' => $this->fullyQualifiedBackupName($this->name), - 'expireTime' => $expireTime->format('Y-m-d\TH:i:s.u\Z') - ] + $options); + 'sourceBackup' => $this->fullyQualifiedBackupName($this->name), + 'expireTime' => $this->formatTimestampForApi($expireTime->format('Y-m-d\TH:i:s.u\Z')) + ]; - return $this->resumeOperation($operation['name'], $operation); + $request = $this->serializer->decodeMessage(new CopyBackupRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); + + return $this->databaseAdminClient->copyBackup($request, $callOptions) + ->withResultFunction($this->backupResultFunction()); } /** @@ -242,7 +180,15 @@ public function createCopy(Backup $newBackup, DateTimeInterface $expireTime, arr */ public function delete(array $options = []) { - return $this->connection->deleteBackup(['name' => $this->name] + $options); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'name' => $this->name + ]; + + $request = $this->serializer->decodeMessage(new DeleteBackupRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $this->databaseAdminClient->deleteBackup($request, $callOptions); } /** @@ -318,9 +264,16 @@ public function name() */ public function reload(array $options = []) { - return $this->info = $this->connection->getBackup([ + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ 'name' => $this->name - ] + $options); + ]; + + $request = $this->serializer->decodeMessage(new GetBackupRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->getBackup($request, $callOptions); + return $this->info = $this->handleResponse($response); } /** @@ -368,15 +321,79 @@ public function state(array $options = []) */ public function updateExpireTime(DateTimeInterface $newTimestamp, array $options = []) { - return $this->info = $this->connection->updateBackup([ + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ 'backup' => [ 'name' => $this->name(), - 'expireTime' => $newTimestamp->format('Y-m-d\TH:i:s.u\Z'), + 'expireTime' => $this->formatTimestampForApi( + $newTimestamp->format('Y-m-d\TH:i:s.u\Z') + ), ], 'updateMask' => [ 'paths' => ['expire_time'] ] - ] + $options); + ]; + + $request = $this->serializer->decodeMessage(new UpdateBackupRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->updateBackup($request, $callOptions); + return $this->info = $this->handleResponse($response); + } + + /** + * Resume a Long Running Operation + * + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return OperationResponse + */ + public function resumeOperation($operationName, array $options = []) + { + return (new OperationResponse( + $operationName, + $this->databaseAdminClient->getOperationsClient(), + $options + ))->withResultFunction($this->backupResultFunction()); + } + + /** + * List long running operations. + * + * Example: + * ``` + * $operations = $backup->longRunningOperations(); + * ``` + * + * @param array $options [optional] { + * Configuration Options. + * + * @type string $name The name of the operation collection. + * @type string $filter The standard list filter. + * @type int $pageSize Maximum number of results to return per + * request. + * @type int $resultLimit Limit the number of results returned in total. + * **Defaults to** `0` (return all results). + * @type string $pageToken A previously-returned page token used to + * resume the loading of results from a specific point. + * } + * @return PagedListResponse + */ + public function longRunningOperations(array $options = []) + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); + $request->setName($this->name . '/operations'); + + return $this->buildLongRunningIterator( + [$this->databaseAdminClient->getOperationsClient(), 'listOperations'], + $request, + $callOptions + ); } /** @@ -400,4 +417,35 @@ private function fullyQualifiedBackupName($name) } //@codeCoverageIgnoreEnd } + + /** + * @param array $options + * @return array + */ + private function validateAndFormatVersionTime(array $options) + { + if (isset($options['versionTime'])) { + if (!($options['versionTime'] instanceof DateTimeInterface)) { + throw new \InvalidArgumentException( + 'Optional argument `versionTime` must be a DateTimeInterface, got ' . + (is_object($options['versionTime']) + ? get_class($options['versionTime']) + : gettype($options['versionTime'])) + ); + } + $options['versionTime'] = $this->formatTimestampForApi( + $options['versionTime']->format('Y-m-d\TH:i:s.u\Z') + ); + } + return $options; + } + + private function backupResultFunction(): Closure + { + return function (BackupProto $backup) { + $name = DatabaseAdminClient::parseName($backup->getName()); + $info = $this->serializer->decodeMessage($backup); + return $this->instance->backup($name['name'], $info); + }; + } } diff --git a/Spanner/src/Batch/BatchClient.php b/Spanner/src/Batch/BatchClient.php index 1c72a60cf7aa..be35db4304fc 100644 --- a/Spanner/src/Batch/BatchClient.php +++ b/Spanner/src/Batch/BatchClient.php @@ -18,10 +18,10 @@ namespace Google\Cloud\Spanner\Batch; use Google\Cloud\Core\TimeTrait; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\TransactionConfigurationTrait; +use Google\Protobuf\Duration; /** * Provides Batch APIs used to read data from a Cloud Spanner database. @@ -36,7 +36,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $batch = $spanner->batch('instance-id', 'database-id'); * ``` * diff --git a/Spanner/src/Batch/BatchSnapshot.php b/Spanner/src/Batch/BatchSnapshot.php index 301ff4ae8f44..258660a382cb 100644 --- a/Spanner/src/Batch/BatchSnapshot.php +++ b/Spanner/src/Batch/BatchSnapshot.php @@ -41,7 +41,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $batch = $spanner->batch('instance-id', 'database-id'); * $snapshot = $batch->snapshot(); * ``` diff --git a/Spanner/src/Batch/QueryPartition.php b/Spanner/src/Batch/QueryPartition.php index a8fa0a3411ca..a29868007b57 100644 --- a/Spanner/src/Batch/QueryPartition.php +++ b/Spanner/src/Batch/QueryPartition.php @@ -17,8 +17,6 @@ namespace Google\Cloud\Spanner\Batch; -use Google\Cloud\Spanner\Session\Session; - /** * Represents a Query Partition. * @@ -35,7 +33,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $batch = $spanner->batch('instance-id', 'database-id'); * $snapshot = $batch->snapshot(); * diff --git a/Spanner/src/Batch/ReadPartition.php b/Spanner/src/Batch/ReadPartition.php index 22375864bb11..563c519f1594 100644 --- a/Spanner/src/Batch/ReadPartition.php +++ b/Spanner/src/Batch/ReadPartition.php @@ -36,7 +36,7 @@ * use Google\Cloud\Spanner\KeySet; * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $batch = $spanner->batch('instance-id', 'database-id'); * $snapshot = $batch->snapshot(); * diff --git a/Spanner/src/BatchDmlResult.php b/Spanner/src/BatchDmlResult.php index f375d8ea6a0e..25fa169f5f92 100644 --- a/Spanner/src/BatchDmlResult.php +++ b/Spanner/src/BatchDmlResult.php @@ -25,7 +25,7 @@ * use Google\Cloud\Spanner\SpannerClient; * use Google\Cloud\Spanner\Transaction; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $database = $spanner->connect('my-instance', 'my-database'); * * $batchDmlResult = $database->runTransaction(function (Transaction $t) { diff --git a/Spanner/src/Bytes.php b/Spanner/src/Bytes.php index 0fc38020c079..7d7120031dcd 100644 --- a/Spanner/src/Bytes.php +++ b/Spanner/src/Bytes.php @@ -28,7 +28,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $bytes = $spanner->bytes('hello world'); * ``` diff --git a/Spanner/src/CommitTimestamp.php b/Spanner/src/CommitTimestamp.php index d5aa05e59fe4..6b6caf91cf0d 100644 --- a/Spanner/src/CommitTimestamp.php +++ b/Spanner/src/CommitTimestamp.php @@ -41,7 +41,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $database = $spanner->connect('my-instance', 'my-database'); * * $database->insert('myTable', [ diff --git a/Spanner/src/Connection/ConnectionInterface.php b/Spanner/src/Connection/ConnectionInterface.php deleted file mode 100644 index 24f417672720..000000000000 --- a/Spanner/src/Connection/ConnectionInterface.php +++ /dev/null @@ -1,291 +0,0 @@ - 'setInsert', - 'update' => 'setUpdate', - 'insertOrUpdate' => 'setInsertOrUpdate', - 'replace' => 'setReplace', - 'delete' => 'setDelete' - ]; - - /** - * @var array - */ - private $lroResponseMappers = [ - [ - 'method' => 'updateDatabaseDdl', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata', - 'message' => UpdateDatabaseDdlMetadata::class - ], [ - 'method' => 'createDatabase', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateDatabaseMetadata', - 'message' => CreateDatabaseMetadata::class - ], [ - 'method' => 'createInstanceConfig', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata', - 'message' => CreateInstanceConfigMetadata::class - ], [ - 'method' => 'updateInstanceConfig', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata', - 'message' => UpdateInstanceConfigMetadata::class - ], [ - 'method' => 'createInstance', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceMetadata', - 'message' => CreateInstanceMetadata::class - ], [ - 'method' => 'updateInstance', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceMetadata', - 'message' => UpdateInstanceMetadata::class - ], [ - 'method' => 'createBackup', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata', - 'message' => CreateBackupMetadata::class - ], [ - 'method' => 'copyBackup', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata', - 'message' => CopyBackupMetadata::class - ], [ - 'method' => 'restoreDatabase', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata', - 'message' => RestoreDatabaseMetadata::class - ], [ - 'method' => 'restoreDatabase', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata', - 'message' => OptimizeRestoredDatabaseMetadata::class - ], [ - 'method' => 'updateDatabaseDdl', - 'typeUrl' => 'type.googleapis.com/google.protobuf.Empty', - 'message' => GPBEmpty::class - ], [ - 'method' => 'createDatabase', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.Database', - 'message' => Database::class - ], [ - 'method' => 'createInstanceConfig', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.InstanceConfig', - 'message' => InstanceConfig::class - ], [ - 'method' => 'updateInstanceConfig', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.InstanceConfig', - 'message' => InstanceConfig::class - ], [ - 'method' => 'createInstance', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.Instance', - 'message' => Instance::class - ], [ - 'method' => 'updateInstance', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.Instance', - 'message' => Instance::class - ], [ - 'method' => 'createBackup', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.Backup', - 'message' => Backup::class - ], [ - 'method' => 'restoreDatabase', - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.Database', - 'message' => Database::class - ] - ]; - - /** - * @var CredentialsWrapper - */ - private $credentialsWrapper; - - /** - * @var bool - */ - private $larEnabled; - - /** - * @param array $config [optional] - */ - public function __construct(array $config = []) - { - //@codeCoverageIgnoreStart - $this->serializer = new Serializer([], [ - 'google.protobuf.Value' => function ($v) { - return $this->flattenValue($v); - }, - 'google.protobuf.ListValue' => function ($v) { - return $this->flattenListValue($v); - }, - 'google.protobuf.Struct' => function ($v) { - return $this->flattenStruct($v); - }, - 'google.protobuf.Timestamp' => function ($v) { - return $this->formatTimestampFromApi($v); - } - ], [], [], [ - // A custom encoder that short-circuits the encodeMessage in Serializer class, - // but only if the argument is of the type PartialResultSet. - PartialResultSet::class => function ($msg) { - $data = json_decode($msg->serializeToJsonString(), true); - - // We only override metadata fields, if it actually exists in the response. - // This is specially important for large data sets which is received in chunks. - // Metadata is only received in the first 'chunk' and we don't want to set empty metadata fields - // when metadata was not returned from the server. - if (isset($data['metadata'])) { - // The transaction id is serialized as a base64 encoded string in $data. So, we - // add a step to get the transaction id using a getter instead of the serialized value. - // The null-safe operator is used to handle edge cases where the relevant fields are not present. - $data['metadata']['transaction']['id'] = (string) $msg?->getMetadata()?->getTransaction()?->getId(); - - // Helps convert metadata enum values from string types to their respective code/annotation - // pairs. Ex: INT64 is converted to {code: 2, typeAnnotation: 0}. - $fields = $msg->getMetadata()?->getRowType()?->getFields(); - $data['metadata']['rowType']['fields'] = $this->getFieldDataFromRepeatedFields($fields); - } - - // These fields in stats should be an int - if (isset($data['stats']['rowCountLowerBound'])) { - $data['stats']['rowCountLowerBound'] = (int) $data['stats']['rowCountLowerBound']; - } - if (isset($data['stats']['rowCountExact'])) { - $data['stats']['rowCountExact'] = (int) $data['stats']['rowCountExact']; - } - - return $data; - } - ]); - //@codeCoverageIgnoreEnd - - $config['serializer'] = $this->serializer; - $this->setRequestWrapper(new GrpcRequestWrapper($config)); - $grpcConfig = $this->getGaxConfig( - ManualSpannerClient::VERSION, - isset($config['authHttpHandler']) - ? $config['authHttpHandler'] - : null - ); - - $config += [ - 'emulatorHost' => null, - 'queryOptions' => [] - ]; - if ((bool) $config['emulatorHost']) { - $grpcConfig = array_merge( - $grpcConfig, - $this->emulatorGapicConfig($config['emulatorHost']) - ); - } elseif (isset($config['apiEndpoint'])) { - $grpcConfig['apiEndpoint'] = $config['apiEndpoint']; - } - $this->credentialsWrapper = $grpcConfig['credentials']; - - $this->defaultQueryOptions = $config['queryOptions']; - - $this->spannerClient = $config['gapicSpannerClient'] - ?? $this->constructGapic(SpannerClient::class, $grpcConfig); - - //@codeCoverageIgnoreStart - if (isset($config['gapicSpannerInstanceAdminClient'])) { - $this->instanceAdminClient = $config['gapicSpannerInstanceAdminClient']; - } - - if (isset($config['gapicSpannerDatabaseAdminClient'])) { - $this->databaseAdminClient = $config['gapicSpannerDatabaseAdminClient']; - } - //@codeCoverageIgnoreEnd - - $this->grpcConfig = $grpcConfig; - $this->larEnabled = $this->pluck('routeToLeader', $config, false) ?? true; - } - - /** - * @param array $args - */ - public function listInstanceConfigs(array $args) - { - $projectName = $this->pluck('projectName', $args); - return $this->send([$this->getInstanceAdminClient(), 'listInstanceConfigs'], [ - $projectName, - $this->addResourcePrefixHeader($args, $projectName) - ]); - } - - /** - * @param array $args - */ - public function getInstanceConfig(array $args) - { - $projectName = $this->pluck('projectName', $args); - return $this->send([$this->getInstanceAdminClient(), 'getInstanceConfig'], [ - $this->pluck('name', $args), - $this->addResourcePrefixHeader($args, $projectName) - ]); - } - - /** - * @param array $args - */ - public function createInstanceConfig(array $args) - { - $instanceConfigName = $args['name']; - - $instanceConfig = $this->instanceConfigObject($args, true); - $res = $this->send([$this->getInstanceAdminClient(), 'createInstanceConfig'], [ - $this->pluck('projectName', $args), - $this->pluck('instanceConfigId', $args), - $instanceConfig, - $this->addResourcePrefixHeader($args, $instanceConfigName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function updateInstanceConfig(array $args) - { - $instanceConfigName = $args['name']; - - $instanceConfigArray = $this->instanceConfigArray($args); - - $fieldMask = $this->fieldMask($instanceConfigArray); - - $instanceConfigObject = $this->serializer->decodeMessage(new InstanceConfig(), $instanceConfigArray); - - $res = $this->send([$this->getInstanceAdminClient(), 'updateInstanceConfig'], [ - $instanceConfigObject, - $fieldMask, - $this->addResourcePrefixHeader($args, $instanceConfigName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function deleteInstanceConfig(array $args) - { - $instanceConfigName = $this->pluck('name', $args); - return $this->send([$this->getInstanceAdminClient(), 'deleteInstanceConfig'], [ - $instanceConfigName, - $this->addResourcePrefixHeader($args, $instanceConfigName) - ]); - } - - /** - * @param array $args - */ - public function listInstanceConfigOperations(array $args) - { - $projectName = $this->pluck('projectName', $args); - $result = $this->send([$this->getInstanceAdminClient(), 'listInstanceConfigOperations'], [ - $projectName, - $this->addResourcePrefixHeader($args, $projectName) - ]); - foreach ($result['operations'] as $index => $operation) { - $result['operations'][$index] = $this->deserializeOperationArray($operation); - } - return $result; - } - - /** - * @param array $args - */ - public function listInstances(array $args) - { - $projectName = $this->pluck('projectName', $args); - return $this->send([$this->getInstanceAdminClient(), 'listInstances'], [ - $projectName, - $this->addResourcePrefixHeader($args, $projectName) - ]); - } - - /** - * @param array $args - */ - public function getInstance(array $args) - { - $projectName = $this->pluck('projectName', $args); - - if (isset($args['fieldMask'])) { - $mask = []; - if (is_array($args['fieldMask'])) { - foreach (array_values($args['fieldMask']) as $field) { - $mask[] = Serializer::toSnakeCase($field); - } - } else { - $mask[] = Serializer::toSnakeCase($args['fieldMask']); - } - $fieldMask = $this->serializer->decodeMessage(new FieldMask(), ['paths' => $mask]); - $args['fieldMask'] = $fieldMask; - } - - return $this->send([$this->getInstanceAdminClient(), 'getInstance'], [ - $this->pluck('name', $args), - $this->addResourcePrefixHeader($args, $projectName) - ]); - } - - /** - * @param array $args - */ - public function createInstance(array $args) - { - $instanceName = $args['name']; - - $instance = $this->instanceObject($args, true); - $res = $this->send([$this->getInstanceAdminClient(), 'createInstance'], [ - $this->pluck('projectName', $args), - $this->pluck('instanceId', $args), - $instance, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function updateInstance(array $args) - { - $instanceName = $args['name']; - - $instanceArray = $this->instanceArray($args); - - $fieldMask = $this->fieldMask($instanceArray); - - $instanceObject = $this->serializer->decodeMessage(new Instance(), $instanceArray); - - $res = $this->send([$this->getInstanceAdminClient(), 'updateInstance'], [ - $instanceObject, - $fieldMask, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function deleteInstance(array $args) - { - $instanceName = $this->pluck('name', $args); - return $this->send([$this->getInstanceAdminClient(), 'deleteInstance'], [ - $instanceName, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - } - - /** - * @param array $args - */ - public function getInstanceIamPolicy(array $args) - { - $resource = $this->pluck('resource', $args); - return $this->send([$this->getInstanceAdminClient(), 'getIamPolicy'], [ - $resource, - $this->addResourcePrefixHeader($args, $resource) - ]); - } - - /** - * @param array $args - */ - public function setInstanceIamPolicy(array $args) - { - $resource = $this->pluck('resource', $args); - return $this->send([$this->getInstanceAdminClient(), 'setIamPolicy'], [ - $resource, - $this->pluck('policy', $args), - $this->addResourcePrefixHeader($args, $resource) - ]); - } - - /** - * @param array $args - */ - public function testInstanceIamPermissions(array $args) - { - $resource = $this->pluck('resource', $args); - return $this->send([$this->getInstanceAdminClient(), 'testIamPermissions'], [ - $resource, - $this->pluck('permissions', $args), - $this->addResourcePrefixHeader($args, $resource) - ]); - } - - /** - * @param array $args - */ - public function listBackups(array $args) - { - $instanceName = $this->pluck('instance', $args); - return $this->send([$this->getDatabaseAdminClient(), 'listBackups'], [ - $instanceName, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - } - - /** - * @param array $args - */ - public function listBackupOperations(array $args) - { - $instanceName = $this->pluck('instance', $args); - $result = $this->send([$this->getDatabaseAdminClient(), 'listBackupOperations'], [ - $instanceName, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - foreach ($result['operations'] as $index => $operation) { - $result['operations'][$index] = $this->deserializeOperationArray($operation); - } - return $result; - } - - /** - * @param array $args - */ - public function listDatabaseOperations(array $args) - { - $instanceName = $this->pluck('instance', $args); - $result = $this->send([$this->getDatabaseAdminClient(), 'listDatabaseOperations'], [ - $instanceName, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - foreach ($result['operations'] as $index => $operation) { - $result['operations'][$index] = $this->deserializeOperationArray($operation); - } - return $result; - } - - /** - * @param array $args - */ - public function restoreDatabase(array $args) - { - $instanceName = $this->pluck('instance', $args); - if (isset($args['encryptionConfig'])) { - $args['encryptionConfig'] = $this->serializer->decodeMessage( - new RestoreDatabaseEncryptionConfig, - $this->pluck('encryptionConfig', $args) - ); - } - $res = $this->send([$this->getDatabaseAdminClient(), 'restoreDatabase'], [ - $instanceName, - $this->pluck('databaseId', $args), - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function updateBackup(array $args) - { - $backup = $this->pluck('backup', $args); - $backup['expireTime'] = $this->formatTimestampForApi($this->pluck('expireTime', $backup)); - $backupInfo = $this->serializer->decodeMessage(new Backup(), $backup); - - $backupName = $backupInfo->getName(); - $updateMask = $this->serializer->decodeMessage(new FieldMask(), $this->pluck('updateMask', $args)); - return $this->send([$this->getDatabaseAdminClient(), 'updateBackup'], [ - $backupInfo, - $updateMask, - $this->addResourcePrefixHeader($args, $backupName) - ]); - } - - /** - * @param array $args - */ - public function createBackup(array $args) - { - $backup = $this->pluck('backup', $args); - $backup['expireTime'] = $this->formatTimestampForApi($this->pluck('expireTime', $backup)); - if (isset($args['versionTime'])) { - $backup['versionTime'] = $this->formatTimestampForApi($this->pluck('versionTime', $args)); - } - $backupInfo = $this->serializer->decodeMessage(new Backup(), $backup); - - $instanceName = $this->pluck('instance', $args); - if (isset($args['encryptionConfig'])) { - $args['encryptionConfig'] = $this->serializer->decodeMessage( - new CreateBackupEncryptionConfig, - $this->pluck('encryptionConfig', $args) - ); - } - $res = $this->send([$this->getDatabaseAdminClient(), 'createBackup'], [ - $instanceName, - $this->pluck('backupId', $args), - $backupInfo, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function copyBackup(array $args) - { - $instanceName = $this->pluck('instance', $args); - $expireTime = new Timestamp( - $this->formatTimestampForApi($this->pluck('expireTime', $args)) - ); - - $res = $this->send([$this->getDatabaseAdminClient(), 'copyBackup'], [ - $instanceName, - $this->pluck('backupId', $args), - $this->pluck('sourceBackupId', $args), - $expireTime, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function deleteBackup(array $args) - { - $backupName = $this->pluck('name', $args); - return $this->send([$this->getDatabaseAdminClient(), 'deleteBackup'], [ - $backupName, - $this->addResourcePrefixHeader($args, $backupName) - ]); - } - - /** - * @param array $args - */ - public function getBackup(array $args) - { - $backupName = $this->pluck('name', $args); - return $this->send([$this->getDatabaseAdminClient(), 'getBackup'], [ - $backupName, - $this->addResourcePrefixHeader($args, $backupName) - ]); - } - - /** - * @param array $args - */ - public function listDatabases(array $args) - { - $instanceName = $this->pluck('instance', $args); - return $this->send([$this->getDatabaseAdminClient(), 'listDatabases'], [ - $instanceName, - $this->addResourcePrefixHeader($args, $instanceName) - ]); - } - - /** - * @param array $args - */ - public function createDatabase(array $args) - { - $instanceName = $this->pluck('instance', $args); - if (isset($args['encryptionConfig'])) { - $args['encryptionConfig'] = $this->serializer->decodeMessage( - new EncryptionConfig, - $this->pluck('encryptionConfig', $args) - ); - } - $res = $this->send([$this->getDatabaseAdminClient(), 'createDatabase'], [ - $instanceName, - $this->pluck('createStatement', $args), - $this->addResourcePrefixHeader($args, $instanceName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function updateDatabase(array $args) - { - $databaseInfo = $this->serializer->decodeMessage(new Database(), $this->pluck('database', $args)); - $databaseName = $databaseInfo->getName(); - $updateMask = $this->serializer->decodeMessage(new FieldMask(), $this->pluck('updateMask', $args)); - return $this->send([$this->getDatabaseAdminClient(), 'updateDatabase'], [ - $databaseInfo, - $updateMask, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function updateDatabaseDdl(array $args) - { - $databaseName = $this->pluck('name', $args); - $res = $this->send([$this->getDatabaseAdminClient(), 'updateDatabaseDdl'], [ - $databaseName, - $this->pluck('statements', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - - return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function dropDatabase(array $args) - { - $databaseName = $this->pluck('name', $args); - return $this->send([$this->getDatabaseAdminClient(), 'dropDatabase'], [ - $databaseName, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function getDatabase(array $args) - { - $databaseName = $this->pluck('name', $args); - return $this->send([$this->getDatabaseAdminClient(), 'getDatabase'], [ - $databaseName, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function getDatabaseDdl(array $args) - { - $databaseName = $this->pluck('name', $args); - return $this->send([$this->getDatabaseAdminClient(), 'getDatabaseDdl'], [ - $databaseName, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function getDatabaseIamPolicy(array $args) - { - $databaseName = $this->pluck('resource', $args); - return $this->send([$this->getDatabaseAdminClient(), 'getIamPolicy'], [ - $databaseName, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function setDatabaseIamPolicy(array $args) - { - $databaseName = $this->pluck('resource', $args); - return $this->send([$this->getDatabaseAdminClient(), 'setIamPolicy'], [ - $databaseName, - $this->pluck('policy', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function testDatabaseIamPermissions(array $args) - { - $databaseName = $this->pluck('resource', $args); - return $this->send([$this->getDatabaseAdminClient(), 'testIamPermissions'], [ - $databaseName, - $this->pluck('permissions', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function createSession(array $args) - { - $databaseName = $this->pluck('database', $args); - - $session = $this->pluck('session', $args, false); - if ($session) { - $args['session'] = $this->serializer->decodeMessage( - new Session, - array_filter( - $session, - function ($value) { - return !is_null($value); - } - ) - ); - } - - $args = $this->addLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'createSession'], [ - $databaseName, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * Note: This should be removed once GAPIC exposes the ability to execute - * concurrent requests. - * - * @access private - * @experimental - * @param array $args - * @return PromiseInterface - */ - public function createSessionAsync(array $args) - { - $databaseName = $this->pluck('database', $args); - $opts = $this->addResourcePrefixHeader([], $databaseName); - $opts = $this->addLarHeader($opts, $this->larEnabled); - $opts['credentialsWrapper'] = $this->credentialsWrapper; - $transport = $this->spannerClient->getTransport(); - - $request = new CreateSessionRequest([ - 'database' => $databaseName - ]); - - $session = $this->pluck('session', $args, false); - - if ($session) { - $sessionMessage = new Session($session); - $request->setSession($sessionMessage); - } - - return $transport->startUnaryCall( - new Call( - 'google.spanner.v1.Spanner/CreateSession', - Session::class, - $request - ), - $opts - ); - } - - /** - * @param array $args - */ - public function batchCreateSessions(array $args) - { - $args['sessionTemplate'] = $this->serializer->decodeMessage( - new Session, - $this->pluck('sessionTemplate', $args) - ); - - $databaseName = $this->pluck('database', $args); - $args = $this->addLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'batchCreateSessions'], [ - $databaseName, - $this->pluck('sessionCount', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function getSession(array $args) - { - $databaseName = $this->pluck('database', $args); - $args = $this->addLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'getSession'], [ - $this->pluck('name', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function deleteSession(array $args) - { - $databaseName = $this->pluck('database', $args); - return $this->send([$this->spannerClient, 'deleteSession'], [ - $this->pluck('name', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * Note: This should be removed once GAPIC exposes the ability to execute - * concurrent requests. - * - * @access private - * @param array $args - * @return PromiseInterface - * @experimental - */ - public function deleteSessionAsync(array $args) - { - $databaseName = $this->pluck('database', $args); - $request = new DeleteSessionRequest(); - $request->setName($this->pluck('name', $args)); - - $transport = $this->spannerClient->getTransport(); - $opts = $this->addResourcePrefixHeader([], $databaseName); - $opts['credentialsWrapper'] = $this->credentialsWrapper; - - return $transport->startUnaryCall( - new Call( - 'google.spanner.v1.Spanner/DeleteSession', - GPBEmpty::class, - $request - ), - $opts - ); - } - - /** - * @param array $args - * @return \Generator - */ - public function executeStreamingSql(array $args) - { - $args = $this->formatSqlParams($args); - $args['transaction'] = $this->createTransactionSelector($args); - - $databaseName = $this->pluck('database', $args); - $queryOptions = $this->pluck('queryOptions', $args, false) ?: []; - - // Query options precedence is query-level, then environment-level, then client-level. - $envQueryOptimizerVersion = getenv('SPANNER_OPTIMIZER_VERSION'); - $envQueryOptimizerStatisticsPackage = getenv('SPANNER_OPTIMIZER_STATISTICS_PACKAGE'); - if (!empty($envQueryOptimizerVersion)) { - $queryOptions += ['optimizerVersion' => $envQueryOptimizerVersion]; - } - if (!empty($envQueryOptimizerStatisticsPackage)) { - $queryOptions += ['optimizerStatisticsPackage' => $envQueryOptimizerStatisticsPackage]; - } - $queryOptions += $this->defaultQueryOptions; - $this->setDirectedReadOptions($args); - - if ($queryOptions) { - $args['queryOptions'] = $this->serializer->decodeMessage( - new QueryOptions, - $queryOptions - ); - } - - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - - $args = $this->conditionallyUnsetLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'executeStreamingSql'], [ - $this->pluck('session', $args), - $this->pluck('sql', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - * @return \Generator - */ - public function streamingRead(array $args) - { - $keySet = $this->pluck('keySet', $args); - $keySet = $this->serializer->decodeMessage(new KeySet, $this->formatKeySet($keySet)); - - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - $this->setDirectedReadOptions($args); - $args['transaction'] = $this->createTransactionSelector($args); - - $databaseName = $this->pluck('database', $args); - $args = $this->conditionallyUnsetLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'streamingRead'], [ - $this->pluck('session', $args), - $this->pluck('table', $args), - $this->pluck('columns', $args), - $keySet, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function executeBatchDml(array $args) - { - $databaseName = $this->pluck('database', $args); - $args['transaction'] = $this->createTransactionSelector($args); - $args = $this->addLarHeader($args, $this->larEnabled); - - $statements = []; - foreach ($this->pluck('statements', $args) as $statement) { - $statement = $this->formatSqlParams($statement); - $statements[] = $this->serializer->decodeMessage(new Statement, $statement); - } - - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - - return $this->send([$this->spannerClient, 'executeBatchDml'], [ - $this->pluck('session', $args), - $this->pluck('transaction', $args), - $statements, - $this->pluck('seqno', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function beginTransaction(array $args) - { - $options = new TransactionOptions; - $transactionOptions = $this->formatTransactionOptions($this->pluck('transactionOptions', $args)); - if (isset($transactionOptions['readOnly'])) { - $readOnlyClass = PHP_VERSION_ID >= 80100 - ? PBReadOnly::class - : 'Google\Cloud\Spanner\V1\TransactionOptions\ReadOnly'; - $readOnly = $this->serializer->decodeMessage( - new $readOnlyClass(), // @phpstan-ignore-line - $transactionOptions['readOnly'] - ); - $options->setReadOnly($readOnly); - } elseif (isset($transactionOptions['readWrite'])) { - $readWrite = new ReadWrite(); - $options->setReadWrite($readWrite); - $args = $this->addLarHeader($args, $this->larEnabled); - } elseif (isset($transactionOptions['partitionedDml'])) { - $pdml = new PartitionedDml(); - $options->setPartitionedDml($pdml); - $args = $this->addLarHeader($args, $this->larEnabled); - } - - // NOTE: if set for read-only actions, will throw exception - if (isset($transactionOptions['excludeTxnFromChangeStreams'])) { - $options->setExcludeTxnFromChangeStreams( - $transactionOptions['excludeTxnFromChangeStreams'] - ); - } - - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - - $databaseName = $this->pluck('database', $args); - return $this->send([$this->spannerClient, 'beginTransaction'], [ - $this->pluck('session', $args), - $options, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function commit(array $args) - { - $inputMutations = $this->pluck('mutations', $args); - - $mutations = $this->parseMutations($inputMutations); - - if (isset($args['singleUseTransaction'])) { - $readWrite = $this->serializer->decodeMessage( - new ReadWrite, - [] - ); - - $options = new TransactionOptions; - $options->setReadWrite($readWrite); - $args['singleUseTransaction'] = $options; - } - - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - - $databaseName = $this->pluck('database', $args); - $args = $this->addLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'commit'], [ - $this->pluck('session', $args), - $mutations, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - * @return \Generator - */ - public function batchWrite(array $args) - { - $databaseName = $this->pluck('database', $args); - $mutationGroups = $this->pluck('mutationGroups', $args); - $requestOptions = $this->pluck('requestOptions', $args, false) ?: []; - - array_walk( - $mutationGroups, - fn(&$x) => $x['mutations'] = $this->parseMutations($x['mutations']) - ); - - $mutationGroups = array_map( - fn($x) => $this->serializer->decodeMessage(new MutationGroupProto(), $x), - $mutationGroups - ); - - if ($requestOptions) { - $args['requestOptions'] = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - } - - return $this->send([$this->spannerClient, 'batchWrite'], [ - $this->pluck('session', $args), - $mutationGroups, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function rollback(array $args) - { - $databaseName = $this->pluck('database', $args); - $args = $this->addLarHeader($args, $this->larEnabled); - return $this->send([$this->spannerClient, 'rollback'], [ - $this->pluck('session', $args), - $this->pluck('transactionId', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function partitionQuery(array $args) - { - $args = $this->formatSqlParams($args); - $args['transaction'] = $this->createTransactionSelector($args); - $args = $this->addLarHeader($args, $this->larEnabled); - - $args['partitionOptions'] = $this->serializer->decodeMessage( - new PartitionOptions, - $this->pluck('partitionOptions', $args, false) ?: [] - ); - - $databaseName = $this->pluck('database', $args); - return $this->send([$this->spannerClient, 'partitionQuery'], [ - $this->pluck('session', $args), - $this->pluck('sql', $args), - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function partitionRead(array $args) - { - $keySet = $this->pluck('keySet', $args); - $keySet = $this->serializer->decodeMessage(new KeySet, $this->formatKeySet($keySet)); - - $args['transaction'] = $this->createTransactionSelector($args); - $args = $this->addLarHeader($args, $this->larEnabled); - - $args['partitionOptions'] = $this->serializer->decodeMessage( - new PartitionOptions, - $this->pluck('partitionOptions', $args, false) ?: [] - ); - - $databaseName = $this->pluck('database', $args); - return $this->send([$this->spannerClient, 'partitionRead'], [ - $this->pluck('session', $args), - $this->pluck('table', $args), - $keySet, - $this->addResourcePrefixHeader($args, $databaseName) - ]); - } - - /** - * @param array $args - */ - public function getOperation(array $args) - { - $name = $this->pluck('name', $args); - - $operation = $this->getOperationByName($this->getDatabaseAdminClient(), $name); - - return $this->operationToArray($operation, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function cancelOperation(array $args) - { - $name = $this->pluck('name', $args); - $method = $this->pluck('method', $args, false); - - $operation = $this->getOperationByName($this->getDatabaseAdminClient(), $name, $method); - $operation->cancel(); - - return $this->operationToArray($operation, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function deleteOperation(array $args) - { - $name = $this->pluck('name', $args); - $method = $this->pluck('method', $args, false); - - $operation = $this->getOperationByName($this->getDatabaseAdminClient(), $name, $method); - $operation->delete(); - - return $this->operationToArray($operation, $this->serializer, $this->lroResponseMappers); - } - - /** - * @param array $args - */ - public function listOperations(array $args) - { - $name = $this->pluck('name', $args, false) ?: ''; - $filter = $this->pluck('filter', $args, false) ?: ''; - - $client = $this->getDatabaseAdminClient()->getOperationsClient(); - - return $this->send([$client, 'listOperations'], [ - $name, - $filter, - $args - ]); - } - - /** - * @param array $args - * @return array - */ - private function formatSqlParams(array $args) - { - $params = $this->pluck('params', $args); - if ($params) { - $modifiedParams = []; - foreach ($params as $key => $param) { - $modifiedParams[$key] = $this->fieldValue($param); - } - $args['params'] = new Struct; - $args['params']->setFields($modifiedParams); - } - - if (isset($args['paramTypes']) && is_array($args['paramTypes'])) { - foreach ($args['paramTypes'] as $key => $param) { - $args['paramTypes'][$key] = $this->serializer->decodeMessage(new Type, $param); - } - } - - return $args; - } - - /** - * @param array $keySet - * @return array Formatted keyset - */ - private function formatKeySet(array $keySet) - { - $keys = $this->pluck('keys', $keySet, false); - if ($keys) { - $keySet['keys'] = []; - - foreach ($keys as $key) { - $keySet['keys'][] = $this->formatListForApi((array) $key); - } - } - - if (isset($keySet['ranges'])) { - foreach ($keySet['ranges'] as $index => $rangeItem) { - foreach ($rangeItem as $key => $val) { - $rangeItem[$key] = $this->formatListForApi($val); - } - - $keySet['ranges'][$index] = $rangeItem; - } - - if (empty($keySet['ranges'])) { - unset($keySet['ranges']); - } - } - - return $keySet; - } - - /** - * @param array $args - * @return TransactionSelector - */ - private function createTransactionSelector(array &$args) - { - $selector = new TransactionSelector; - if (isset($args['transaction'])) { - $transaction = $this->pluck('transaction', $args); - - if (isset($transaction['singleUse'])) { - $transaction['singleUse'] = $this->formatTransactionOptions($transaction['singleUse']); - } - - if (isset($transaction['begin'])) { - $transaction['begin'] = $this->formatTransactionOptions($transaction['begin']); - } - - $selector = $this->serializer->decodeMessage($selector, $transaction); - } elseif (isset($args['transactionId'])) { - $selector = $this->serializer->decodeMessage($selector, ['id' => $this->pluck('transactionId', $args)]); - } - - return $selector; - } - - /** - * Converts a PHP array to an InstanceConfig proto message. - * - * @param array $args - * @param bool $required - * @return InstanceConfig - */ - private function instanceConfigObject(array &$args, $required = false) - { - return $this->serializer->decodeMessage( - new InstanceConfig(), - $this->instanceConfigArray($args, $required) - ); - } - - /** - * Creates a PHP array with only the fields that are relevant for an InstanceConfig. - * - * @param array $args - * @param bool $required - * @return array - */ - private function instanceConfigArray(array &$args, $required = false) - { - $argsCopy = $args; - return array_intersect_key([ - 'name' => $this->pluck('name', $args, $required), - 'baseConfig' => $this->pluck('baseConfig', $args, $required), - 'displayName' => $this->pluck('displayName', $args, $required), - 'configType' => $this->pluck('configType', $args, $required), - 'replicas' => $this->pluck('replicas', $args, $required), - 'optionalReplicas' => $this->pluck('optionalReplicas', $args, $required), - 'leaderOptions' => $this->pluck('leaderOptions', $args, $required), - 'reconciling' => $this->pluck('reconciling', $args, $required), - 'state' => $this->pluck('state', $args, $required), - 'labels' => $this->pluck('labels', $args, $required), - ], $argsCopy); - } - - /** - * @param array $args - * @param bool $required - * @return Instance - */ - private function instanceObject(array &$args, $required = false) - { - return $this->serializer->decodeMessage( - new Instance(), - $this->instanceArray($args, $required) - ); - } - - /** - * @param array $args - * @param bool $required - * @return array - */ - private function instanceArray(array &$args, $required = false) - { - $argsCopy = $args; - if (isset($args['nodeCount'])) { - return array_intersect_key([ - 'name' => $this->pluck('name', $args, $required), - 'config' => $this->pluck('config', $args, $required), - 'displayName' => $this->pluck('displayName', $args, $required), - 'nodeCount' => $this->pluck('nodeCount', $args, $required), - 'state' => $this->pluck('state', $args, $required), - 'labels' => $this->pluck('labels', $args, $required), - ], $argsCopy); - } - return array_intersect_key([ - 'name' => $this->pluck('name', $args, $required), - 'config' => $this->pluck('config', $args, $required), - 'displayName' => $this->pluck('displayName', $args, $required), - 'processingUnits' => $this->pluck('processingUnits', $args, $required), - 'state' => $this->pluck('state', $args, $required), - 'labels' => $this->pluck('labels', $args, $required), - ], $argsCopy); - } - - /** - * @param array $instanceArray - * @return FieldMask - */ - private function fieldMask($instanceArray) - { - $mask = []; - foreach (array_keys($instanceArray) as $key) { - if ($key !== 'name') { - $mask[] = Serializer::toSnakeCase($key); - } - } - return $this->serializer->decodeMessage(new FieldMask(), ['paths' => $mask]); - } - - /** - * @param mixed $param - * @return Value - */ - private function fieldValue($param) - { - $field = new Value; - $value = $this->formatValueForApi($param); - - $setter = null; - switch (array_keys($value)[0]) { - case 'string_value': - $setter = 'setStringValue'; - break; - case 'number_value': - $setter = 'setNumberValue'; - break; - case 'bool_value': - $setter = 'setBoolValue'; - break; - case 'null_value': - $setter = 'setNullValue'; - break; - case 'struct_value': - $setter = 'setStructValue'; - $modifiedParams = []; - foreach ($param as $key => $value) { - $modifiedParams[$key] = $this->fieldValue($value); - } - $value = new Struct; - $value->setFields($modifiedParams); - - break; - case 'list_value': - $setter = 'setListValue'; - $modifiedParams = []; - foreach ($param as $item) { - $modifiedParams[] = $this->fieldValue($item); - } - $list = new ListValue; - $list->setValues($modifiedParams); - $value = $list; - - break; - } - - $value = is_array($value) ? current($value) : $value; - if ($setter) { - $field->$setter($value); - } - - return $field; - } - - /** - * @param array $transactionOptions - * @return array - */ - private function formatTransactionOptions(array $transactionOptions) - { - if (isset($transactionOptions['readOnly'])) { - $ro = $transactionOptions['readOnly']; - if (isset($ro['minReadTimestamp'])) { - $ro['minReadTimestamp'] = $this->formatTimestampForApi($ro['minReadTimestamp']); - } - - if (isset($ro['readTimestamp'])) { - $ro['readTimestamp'] = $this->formatTimestampForApi($ro['readTimestamp']); - } - - $transactionOptions['readOnly'] = $ro; - } - - return $transactionOptions; - } - - /** - * Allow lazy instantiation of the instance admin client. - * - * @return InstanceAdminClient - */ - private function getInstanceAdminClient() - { - //@codeCoverageIgnoreStart - if ($this->instanceAdminClient) { - return $this->instanceAdminClient; - } - //@codeCoverageIgnoreEnd - $this->instanceAdminClient = $this->constructGapic(InstanceAdminClient::class, $this->grpcConfig); - - return $this->instanceAdminClient; - } - - /** - * Allow lazy instantiation of the database admin client. - * - * @return DatabaseAdminClient - */ - private function getDatabaseAdminClient() - { - //@codeCoverageIgnoreStart - if ($this->databaseAdminClient) { - return $this->databaseAdminClient; - } - //@codeCoverageIgnoreEnd - - $this->databaseAdminClient = $this->constructGapic(DatabaseAdminClient::class, $this->grpcConfig); - - return $this->databaseAdminClient; - } - - private function deserializeOperationArray($operation) - { - $operation['metadata'] = - $this->deserializeMessageArray($operation['metadata']) + - ['typeUrl' => $operation['metadata']['typeUrl']]; - - if (isset($operation['response']) and isset($operation['response']['typeUrl'])) { - $operation['response'] = $this->deserializeMessageArray($operation['response']); - } - - return $operation; - } - - private function deserializeMessageArray($message) - { - $typeUrl = $message['typeUrl']; - $mapper = $this->getLroResponseMapper($typeUrl); - if (!isset($mapper)) { - return $message; - } - - $className = $mapper['message']; - $response = new $className; - $response->mergeFromString($message['value']); - return $this->serializer->encodeMessage($response); - } - - private function getLroResponseMapper($typeUrl) - { - foreach ($this->lroResponseMappers as $mapper) { - if ($mapper['typeUrl'] == $typeUrl) { - return $mapper; - } - } - - return null; - } - - /** - * Set DirectedReadOptions if provided. - * - * @param array $args - */ - private function setDirectedReadOptions(array &$args) - { - $directedReadOptions = $this->pluck('directedReadOptions', $args, false); - if (!empty($directedReadOptions)) { - $args['directedReadOptions'] = $this->serializer->decodeMessage( - new DirectedReadOptions, - $directedReadOptions - ); - } - } - - /** - * Utiltiy method to take in a Google\Cloud\Spanner\V1\Type value and return - * the data as an array. The method takes care of array and struct elements. - * - * @param Type $type The "type" object - * - * @return array The formatted data. - */ - private function getTypeData(Type $type): array - { - $data = [ - 'code' => $type->getCode(), - 'typeAnnotation' => $type->getTypeAnnotation(), - 'protoTypeFqn' => $type->getProtoTypeFqn() - ]; - - // If this is a struct field, then recursisevly call getTypeData - if ($type->hasStructType()) { - $nestedType = $type->getStructType(); - $fields = $nestedType->getFields(); - $data['structType'] = [ - 'fields' => $this->getFieldDataFromRepeatedFields($fields) - ]; - } - // If this is an array field, then recursisevly call getTypeData - if ($type->hasArrayElementType()) { - $nestedType = $type->getArrayElementType(); - $data['arrayElementType'] = $this->getTypeData($nestedType); - } - - return $data; - } - - /** - * Utility method to return "fields data" in the format: - * [ - * "name" => "" - * "type" => [] - * ]. - * - * The type is converted from a string like INT64 to ["code" => 2, "typeAnnotation" => 0] - * conforming with the Google\Cloud\Spanner\V1\TypeCode class. - * - * @param ?RepeatedField $fields The array contain list of fields. - * - * @return array The formatted fields data. - */ - private function getFieldDataFromRepeatedFields(?RepeatedField $fields): array - { - if (is_null($fields)) { - return []; - } - - $fieldsData = []; - foreach ($fields as $key => $field) { - $type = $field->getType(); - $typeData = $this->getTypeData($type); - - $fieldsData[$key] = [ - 'name' => $field->getName(), - 'type' => $typeData - ]; - } - - return $fieldsData; - } - - private function parseMutations($rawMutations) - { - if (!is_array($rawMutations)) { - return []; - } - - $mutations = []; - foreach ($rawMutations as $mutation) { - $type = array_keys($mutation)[0]; - $data = $mutation[$type]; - - switch ($type) { - case Operation::OP_DELETE: - if (isset($data['keySet'])) { - $data['keySet'] = $this->formatKeySet($data['keySet']); - } - - $operation = $this->serializer->decodeMessage( - new Delete, - $data - ); - break; - default: - $operation = new Write; - $operation->setTable($data['table']); - $operation->setColumns($data['columns']); - - $modifiedData = []; - foreach ($data['values'] as $key => $param) { - $modifiedData[$key] = $this->fieldValue($param); - } - - $list = new ListValue; - $list->setValues($modifiedData); - $values = [$list]; - $operation->setValues($values); - - break; - } - - $setterName = $this->mutationSetters[$type]; - $mutation = new Mutation; - $mutation->$setterName($operation); - $mutations[] = $mutation; - } - return $mutations; - } -} diff --git a/Spanner/src/Connection/IamDatabase.php b/Spanner/src/Connection/IamDatabase.php deleted file mode 100644 index 74d4f27f3aba..000000000000 --- a/Spanner/src/Connection/IamDatabase.php +++ /dev/null @@ -1,65 +0,0 @@ -connection = $connection; - } - - /** - * @param array $args - */ - public function getPolicy(array $args) - { - return $this->connection->getDatabaseIamPolicy($args); - } - - /** - * @param array $args - */ - public function setPolicy(array $args) - { - return $this->connection->setDatabaseIamPolicy($args); - } - - /** - * @param array $args - */ - public function testPermissions(array $args) - { - return $this->connection->testDatabaseIamPermissions($args); - } -} diff --git a/Spanner/src/Connection/IamInstance.php b/Spanner/src/Connection/IamInstance.php deleted file mode 100644 index 29fdf910b2a2..000000000000 --- a/Spanner/src/Connection/IamInstance.php +++ /dev/null @@ -1,65 +0,0 @@ -connection = $connection; - } - - /** - * @param array $args - */ - public function getPolicy(array $args) - { - return $this->connection->getInstanceIamPolicy($args); - } - - /** - * @param array $args - */ - public function setPolicy(array $args) - { - return $this->connection->setInstanceIamPolicy($args); - } - - /** - * @param array $args - */ - public function testPermissions(array $args) - { - return $this->connection->testInstanceIamPermissions($args); - } -} diff --git a/Spanner/src/Connection/LongRunningConnection.php b/Spanner/src/Connection/LongRunningConnection.php deleted file mode 100644 index 529bc2354583..000000000000 --- a/Spanner/src/Connection/LongRunningConnection.php +++ /dev/null @@ -1,75 +0,0 @@ -connection = $connection; - } - - /** - * @param array $args - */ - public function get(array $args) - { - return $this->connection->getOperation($args); - } - - /** - * @param array $args - */ - public function cancel(array $args) - { - return $this->connection->cancelOperation($args); - } - - /** - * @param array $args - */ - public function delete(array $args) - { - return $this->connection->deleteOperation($args); - } - - /** - * @param array $args - */ - public function operations(array $args) - { - return $this->connection->listOperations($args); - } -} diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 23e4317e0462..6e69046e5bd2 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -17,30 +17,51 @@ namespace Google\Cloud\Spanner; +use Closure; use Google\ApiCore\ApiException; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\RetrySettings; use Google\ApiCore\ValidationException; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\AbortedException; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Exception\ServiceException; -use Google\Cloud\Core\Iam\Iam; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; -use Google\Cloud\Core\LongRunning\LROTrait; +use Google\Cloud\Core\Iterator\PageIterator; +use Google\Cloud\Core\RequestHandler; +use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Core\Retry; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; use Google\Cloud\Spanner\Admin\Database\V1\Database\State; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Spanner\Connection\IamDatabase; +use Google\Cloud\Spanner\Admin\Database\V1\DropDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseRequest; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; -use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\V1\BatchWriteResponse; -use Google\Cloud\Spanner\V1\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\BatchCreateSessionsRequest; +use Google\Cloud\Spanner\V1\BatchWriteRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +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\TypeCode; +use Google\LongRunning\ListOperationsRequest; +use Google\Protobuf\Duration; +use Google\Protobuf\ListValue; +use Google\Protobuf\Struct; +use Google\Protobuf\Value; use Google\Rpc\Code; +use GuzzleHttp\Promise\PromiseInterface; /** * Represents a Cloud Spanner Database. @@ -49,7 +70,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $database = $spanner->connect('my-instance', 'my-database'); * ``` @@ -58,52 +79,16 @@ * // Databases can also be connected to via an Instance. * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $instance = $spanner->instance('my-instance'); * $database = $instance->database('my-database'); * ``` - * - * @method resumeOperation() { - * Resume a Long Running Operation - * - * Example: - * ``` - * $operation = $database->resumeOperation($operationName); - * ``` - * - * @param string $operationName The Long Running Operation name. - * @param array $info [optional] The operation data. - * @return LongRunningOperation - * } - * @method longRunningOperations() { - * List long running operations. - * - * Example: - * ``` - * $operations = $database->longRunningOperations(); - * ``` - * - * @param array $options [optional] { - * Configuration Options. - * - * @type string $name The name of the operation collection. - * @type string $filter The standard list filter. - * @type int $pageSize Maximum number of results to return per - * request. - * @type int $resultLimit Limit the number of results returned in total. - * **Defaults to** `0` (return all results). - * @type string $pageToken A previously-returned page token used to - * resume the loading of results from a specific point. - * } - * @return ItemIterator - * } */ class Database { - use LROTrait; use TransactionConfigurationTrait; - use RequestHeaderTrait; + use RequestTrait; const STATE_CREATING = State::CREATING; const STATE_READY = State::READY; @@ -126,39 +111,13 @@ class Database const TYPE_JSON = TypeCode::JSON; const TYPE_PG_OID = 'pgOid'; - /** - * @var ConnectionInterface - * @internal - */ - private $connection; - - /** - * @var Instance - */ - private $instance; - /** * @var Operation */ private $operation; /** - * @var string - */ - private $projectId; - - /** - * @var string - */ - private $name; - - /** - * @var array - */ - private $info; - - /** - * @var Iam|null + * @var IamManager|null */ private $iam; @@ -168,40 +127,45 @@ class Database private $session; /** - * @var SessionPoolInterface|null + * @var bool */ - private $sessionPool; + private $isRunningTransaction = false; /** - * @var bool + * @var array */ - private $isRunningTransaction = false; + private $directedReadOptions; /** - * @var string|null + * @var bool */ - private $databaseRole; + private $routeToLeader; /** * @var array */ - private $directedReadOptions; + private $defaultQueryOptions; /** - * @var bool + * @var array */ - private $returnInt64AsObject; + private $mutationSetters = [ + 'insert' => 'setInsert', + 'update' => 'setUpdate', + 'insertOrUpdate' => 'setInsertOrUpdate', + 'replace' => 'setReplace', + 'delete' => 'setDelete' + ]; /** * Create an object representing a Database. * - * @param ConnectionInterface $connection The connection to the - * Cloud Spanner Admin API. This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @internal Database is constructed by the {@see Instance} class. + * + * @param SpannerClient $spannerClient The Spanner client used to interact with the API. + * @param DatabaseAdminClient $databaseAdminClient The database admin client used to interact with the API. + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param Instance $instance The instance in which the database exists. - * @param LongRunningConnectionInterface $lroConnection An implementation - * mapping to methods which handle LRO resolution in the service. - * @param array $lroCallables * @param string $projectId The project ID. * @param string $name The database name or ID. * @param SessionPoolInterface $sessionPool [optional] The session pool @@ -210,35 +174,45 @@ class Database * be returned as a {@see \Google\Cloud\Core\Int64} object for 32 bit * platform compatibility. **Defaults to** false. * @param string $databaseRole The user created database role which creates the session. + * @param string $config [Optional] { + * Configuration options. + * + * @type bool $routeToLeader Enable/disable Leader Aware Routing. + * **Defaults to** `true` (enabled). + * @type array $defaultQueryOptions + * } */ public function __construct( - ConnectionInterface $connection, - Instance $instance, - LongRunningConnectionInterface $lroConnection, - array $lroCallables, - $projectId, - $name, - SessionPoolInterface $sessionPool = null, - $returnInt64AsObject = false, - array $info = [], - $databaseRole = '' + private SpannerClient $spannerClient, + private DatabaseAdminClient $databaseAdminClient, + private Serializer $serializer, + private Instance $instance, + private string $projectId, + private string $name, + private ?SessionPoolInterface $sessionPool = null, + private bool $returnInt64AsObject = false, + private array $info = [], + private string $databaseRole = '', + array $config = [] ) { - $this->connection = $connection; - $this->instance = $instance; - $this->projectId = $projectId; $this->name = $this->fullyQualifiedDatabaseName($name); - $this->sessionPool = $sessionPool; - $this->operation = new Operation($connection, $returnInt64AsObject); - $this->info = $info; + $this->routeToLeader = $config['routeToLeader'] ?? true; + $this->defaultQueryOptions = $config['defaultQueryOptions'] ?? []; + $this->operation = new Operation( + $this->spannerClient, + $serializer, + $returnInt64AsObject, + [ + 'routeToLeader' => $this->routeToLeader, + 'defaultQueryOptions' => $this->defaultQueryOptions + ] + ); if ($this->sessionPool) { $this->sessionPool->setDatabase($this); } - $this->setLroProperties($lroConnection, $lroCallables, $this->name); - $this->databaseRole = $databaseRole; $this->directedReadOptions = $instance->directedReadOptions(); - $this->returnInt64AsObject = $returnInt64AsObject; } /** @@ -260,7 +234,7 @@ public function __construct( * @param array $options [optional] Configuration options. * @return int|null */ - public function state(array $options = []) + public function state(array $options = []): ?int { $info = $this->info($options); @@ -292,9 +266,9 @@ public function state(array $options = []) * * @return ItemIterator */ - public function backups(array $options = []) + public function backups(array $options = []): ItemIterator { - $filter = "database:" . $this->name(); + $filter = 'database:' . $this->name(); if (isset($options['filter'])) { $filter = sprintf('(%1$s) AND (%2$s)', $filter, $this->pluck('filter', $options)); @@ -320,9 +294,9 @@ public function backups(array $options = []) * eligible to be automatically deleted by Cloud Spanner. * @param array $options [optional] Configuration options. * - * @return LongRunningOperation + * @return OperationResponse */ - public function createBackup($name, \DateTimeInterface $expireTime, array $options = []) + public function createBackup($name, \DateTimeInterface $expireTime, array $options = []): OperationResponse { $backup = $this->instance->backup($name); return $backup->create($this->name(), $expireTime, $options); @@ -338,7 +312,7 @@ public function createBackup($name, \DateTimeInterface $expireTime, array $optio * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -358,7 +332,7 @@ public function name() * @param array $options [optional] Configuration options. * @return array */ - public function info(array $options = []) + public function info(array $options = []): array { return $this->info ?: $this->reload($options); } @@ -378,11 +352,16 @@ public function info(array $options = []) * @param array $options [optional] Configuration options. * @return array */ - public function reload(array $options = []) + public function reload(array $options = []): array { - return $this->info = $this->connection->getDatabase([ - 'name' => $this->name - ] + $options); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['name'] = $this->name; + + $request = $this->serializer->decodeMessage(new GetDatabaseRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->getDatabase($request, $callOptions); + return $this->info = $this->handleResponse($response); } /** @@ -402,7 +381,7 @@ public function reload(array $options = []) * @param array $options [optional] Configuration options. * @return bool */ - public function exists(array $options = []) + public function exists(array $options = []): bool { try { $this->reload($options); @@ -430,22 +409,24 @@ public function exists(array $options = []) * * @type string[] $statements Additional DDL statements. * } - * @return LongRunningOperation + * @return OperationResponse */ - public function create(array $options = []) + public function create(array $options = []): OperationResponse { - $statements = $this->pluck('statements', $options, false) ?: []; - $dialect = $options['databaseDialect'] ?? null; + [$data, $callOptions] = $this->splitOptionalArgs($options); + $dialect = $data['databaseDialect'] ?? DatabaseDialect::DATABASE_DIALECT_UNSPECIFIED; - $createStatement = $this->getCreateDbStatement($dialect); + $data += [ + 'parent' => $this->instance->name(), + 'createStatement' => $this->getCreateDbStatement($dialect), + 'extraStatements' => $this->pluck('statements', $data, false) ?: [] + ]; - $operation = $this->connection->createDatabase([ - 'instance' => $this->instance->name(), - 'createStatement' => $createStatement, - 'extraStatements' => $statements - ] + $options); + $request = $this->serializer->decodeMessage(new CreateDatabaseRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); - return $this->resumeOperation($operation['name'], $operation); + return $this->databaseAdminClient->createDatabase($request, $callOptions) + ->withResultFunction($this->databaseResultFunction()); } /** @@ -462,9 +443,9 @@ public function create(array $options = []) * `projects//instances//backups/`. * @param array $options [optional] Configuration options. * - * @return LongRunningOperation + * @return OperationResponse */ - public function restore($backup, array $options = []) + public function restore($backup, array $options = []): OperationResponse { return $this->instance->createDatabaseFromBackup($this->name, $backup, $options); } @@ -487,23 +468,30 @@ public function restore($backup, array $options = []) * @type bool $enableDropProtection If `true`, delete operations for Database * and Instance will be blocked. **Defaults to** `false`. * } - * @return LongRunningOperation + * @return OperationResponse */ - public function updateDatabase(array $options = []) + public function updateDatabase(array $options = []): OperationResponse { + [$data, $callOptions] = $this->splitOptionalArgs($options); $fieldMask = []; - if (isset($options['enableDropProtection'])) { + + if (isset($data['enableDropProtection'])) { $fieldMask[] = 'enable_drop_protection'; } - return $this->connection->updateDatabase([ + $data += [ + 'updateMask' => ['paths' => $fieldMask], 'database' => [ 'name' => $this->name, - 'enableDropProtection' => $options['enableDropProtection'] ?? false, - ], - 'updateMask' => [ - 'paths' => $fieldMask + 'enableDropProtection' => + $this->pluck('enableDropProtection', $data, false) ?: false ] - ] + $options); + ]; + + $request = $this->serializer->decodeMessage(new UpdateDatabaseRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->databaseAdminClient->updateDatabase($request, $callOptions) + ->withResultFunction($this->databaseResultFunction()); } /** @@ -529,9 +517,9 @@ public function updateDatabase(array $options = []) * * @param string $statement A DDL statements to run against a database. * @param array $options [optional] Configuration options. - * @return LongRunningOperation + * @return OperationResponse */ - public function updateDdl($statement, array $options = []) + public function updateDdl($statement, array $options = []): OperationResponse { return $this->updateDdlBatch([$statement], $options); } @@ -564,16 +552,20 @@ public function updateDdl($statement, array $options = []) * * @param string[] $statements A list of DDL statements to run against a database. * @param array $options [optional] Configuration options. - * @return LongRunningOperation + * @return OperationResponse */ - public function updateDdlBatch(array $statements, array $options = []) + public function updateDdlBatch(array $statements, array $options = []): OperationResponse { - $operation = $this->connection->updateDatabaseDdl($options + [ - 'name' => $this->name, - 'statements' => $statements, - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'database' => $this->name, + 'statements' => $statements + ]; - return $this->resumeOperation($operation['name'], $operation); + $request = $this->serializer->decodeMessage(new UpdateDatabaseDdlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions); } /** @@ -598,11 +590,15 @@ public function updateDdlBatch(array $statements, array $options = []) * @param array $options [optional] Configuration options. * @return void */ - public function drop(array $options = []) + public function drop(array $options = []): void { - $this->connection->dropDatabase($options + [ - 'name' => $this->name - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['database'] = $this->name; + + $request = $this->serializer->decodeMessage(new DropDatabaseRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $this->databaseAdminClient->dropDatabase($request, $callOptions); if ($this->sessionPool) { $this->sessionPool->clear(); @@ -631,11 +627,16 @@ public function drop(array $options = []) * @param array $options [optional] Configuration options. * @return array */ - public function ddl(array $options = []) + public function ddl(array $options = []): array { - $ddl = $this->connection->getDatabaseDDL($options + [ - 'name' => $this->name - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['database'] = $this->name; + + $request = $this->serializer->decodeMessage(new GetDatabaseDdlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->getDatabaseDdl($request, $callOptions); + $ddl = $this->handleResponse($response); if (isset($ddl['statements'])) { return $ddl['statements']; @@ -652,13 +653,15 @@ public function ddl(array $options = []) * $iam = $database->iam(); * ``` * - * @return Iam + * @return IamManager */ public function iam() { if (!$this->iam) { - $this->iam = new Iam( - new IamDatabase($this->connection), + $this->iam = new IamManager( + new RequestHandler($this->serializer, [$this->databaseAdminClient]), + $this->serializer, + DatabaseAdminClient::class, $this->name ); } @@ -723,17 +726,13 @@ public function iam() * **Defaults to** `false`. * @type array $sessionOptions Session configuration and request options. * Session labels may be applied using the `labels` key. - * @type array $directedReadOptions Directed read options. - * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions} - * If using the `replicaSelection::type` setting, utilize the constants available in - * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type} to set a value. * } * @return Snapshot * @throws \BadMethodCallException If attempting to call this method within * an existing transaction. * @codingStandardsIgnoreEnd */ - public function snapshot(array $options = []) + public function snapshot(array $options = []): Snapshot { if ($this->isRunningTransaction) { throw new \BadMethodCallException('Nested transactions are not supported by this client.'); @@ -744,10 +743,6 @@ public function snapshot(array $options = []) ]; $options['transactionOptions'] = $this->configureSnapshotOptions($options); - $options['directedReadOptions'] = $this->configureDirectedReadOptions( - $options, - $this->directedReadOptions ?? [] - ); $session = $this->selectSession( SessionPoolInterface::CONTEXT_READ, @@ -802,7 +797,7 @@ public function snapshot(array $options = []) * @throws \BadMethodCallException If attempting to call this method within * an existing transaction. */ - public function transaction(array $options = []) + public function transaction(array $options = []): Transaction { if ($this->isRunningTransaction) { throw new \BadMethodCallException('Nested transactions are not supported by this client.'); @@ -888,8 +883,12 @@ public function transaction(array $options = []) * @param array $options [optional] { * Configuration Options * - * @type int $maxRetries The number of times to attempt to apply the - * operation before failing. **Defaults to ** `10`. + * @type RetrySettings|array $retrySettings { + * Retry configuration options. Currently, only the `maxRetries` option is supported. + * + * @type int $maxRetries The maximum number of retry attempts before the operation fails. + * Defaults to 10. + * } * @type bool $singleUse If true, a Transaction ID will not be allocated * up front. Instead, the transaction will be considered * "single-use", and may be used for only a single operation. Note @@ -911,10 +910,14 @@ public function runTransaction(callable $operation, array $options = []) if ($this->isRunningTransaction) { throw new \BadMethodCallException('Nested transactions are not supported by this client.'); } + $options += ['retrySettings' => ['maxRetries' => self::MAX_RETRIES]]; - $options += [ - 'maxRetries' => self::MAX_RETRIES, - ]; + $retrySettings = $this->pluck('retrySettings', $options); + if ($retrySettings instanceof RetrySettings) { + $maxRetries = $retrySettings->getMaxRetries(); + } else { + $maxRetries = $retrySettings['maxRetries']; + } // There isn't anything configurable here. $options['transactionOptions'] = $this->configureTransactionOptions($options['transactionOptions'] ?? []); @@ -980,7 +983,7 @@ public function runTransaction(callable $operation, array $options = []) return $res; }; - $retry = new Retry($options['maxRetries'], $delayFn); + $retry = new Retry($maxRetries, $delayFn); try { return $retry->execute($transactionFn, [$operation, $session, $options]); @@ -1027,7 +1030,7 @@ public function runTransaction(callable $operation, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function insert($table, array $data, array $options = []) + public function insert(string $table, array $data, array $options = []): Timestamp { return $this->insertBatch($table, [$data], $options); } @@ -1076,7 +1079,7 @@ public function insert($table, array $data, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function insertBatch($table, array $dataSet, array $options = []) + public function insertBatch(string $table, array $dataSet, array $options = []): Timestamp { $mutations = []; foreach ($dataSet as $data) { @@ -1122,7 +1125,7 @@ public function insertBatch($table, array $dataSet, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function update($table, array $data, array $options = []) + public function update(string $table, array $data, array $options = []): Timestamp { return $this->updateBatch($table, [$data], $options); } @@ -1168,7 +1171,7 @@ public function update($table, array $data, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function updateBatch($table, array $dataSet, array $options = []) + public function updateBatch(string $table, array $dataSet, array $options = []): Timestamp { $mutations = []; foreach ($dataSet as $data) { @@ -1215,7 +1218,7 @@ public function updateBatch($table, array $dataSet, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function insertOrUpdate($table, array $data, array $options = []) + public function insertOrUpdate(string $table, array $data, array $options = []): Timestamp { return $this->insertOrUpdateBatch($table, [$data], $options); } @@ -1263,7 +1266,7 @@ public function insertOrUpdate($table, array $data, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function insertOrUpdateBatch($table, array $dataSet, array $options = []) + public function insertOrUpdateBatch(string $table, array $dataSet, array $options = []): Timestamp { $mutations = []; foreach ($dataSet as $data) { @@ -1310,7 +1313,7 @@ public function insertOrUpdateBatch($table, array $dataSet, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function replace($table, array $data, array $options = []) + public function replace(string $table, array $data, array $options = []): Timestamp { return $this->replaceBatch($table, [$data], $options); } @@ -1358,7 +1361,7 @@ public function replace($table, array $data, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function replaceBatch($table, array $dataSet, array $options = []) + public function replaceBatch($table, array $dataSet, array $options = []): Timestamp { $mutations = []; foreach ($dataSet as $data) { @@ -1408,7 +1411,7 @@ public function replaceBatch($table, array $dataSet, array $options = []) * } * @return Timestamp The commit Timestamp. */ - public function delete($table, KeySet $keySet, array $options = []) + public function delete($table, KeySet $keySet, array $options = []): Timestamp { $mutations = [$this->operation->deleteMutation($table, $keySet)]; @@ -1667,7 +1670,7 @@ public function delete($table, KeySet $keySet, array $options = []) * @codingStandardsIgnoreEnd * @return Result */ - public function execute($sql, array $options = []) + public function execute($sql, array $options = []): Result { unset($options['requestOptions']['transactionTag']); $session = $this->pluck('session', $options, false) @@ -1688,6 +1691,8 @@ public function execute($sql, array $options = []) ); try { + // Unset the internal flag. + unset($options['singleUse']); return $this->operation->execute($session, $sql, $options); } finally { $session->setExpiration(); @@ -1699,7 +1704,7 @@ public function execute($sql, array $options = []) * * @return MutationGroup */ - public function mutationGroup() + public function mutationGroup(): MutationGroup { return new MutationGroup($this->returnInt64AsObject); } @@ -1746,11 +1751,11 @@ public function mutationGroup() * transactions. * } * - * @retur \Generator {@see \Google\Cloud\Spanner\V1\BatchWriteResponse} + * @return \Generator {@see \Google\Cloud\Spanner\V1\BatchWriteResponse} * * @throws ApiException if the remote call fails */ - public function batchWrite(array $mutationGroups, array $options = []) + public function batchWrite(array $mutationGroups, array $options = []): \Generator { if ($this->isRunningTransaction) { throw new \BadMethodCallException('Nested transactions are not supported by this client.'); @@ -1764,12 +1769,24 @@ public function batchWrite(array $mutationGroups, array $options = []) $mutationGroups = array_map(fn ($x) => $x->toArray(), $mutationGroups); + array_walk( + $mutationGroups, + fn (&$x) => $x['mutations'] = $this->parseMutations($x['mutations']) + ); + try { - return $this->connection->batchWrite([ - 'database' => $this->name(), + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ 'session' => $session->name(), 'mutationGroups' => $mutationGroups - ] + $options); + ]; + + $request = $this->serializer->decodeMessage(new BatchWriteRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $response = $this->spannerClient->batchWrite($request, $callOptions); + return $this->handleResponse($response); } finally { $this->isRunningTransaction = false; $session->setExpiration(); @@ -1893,7 +1910,7 @@ public function batchWrite(array $mutationGroups, array $options = []) * } * @return int The number of rows modified. */ - public function executePartitionedUpdate($statement, array $options = []) + public function executePartitionedUpdate($statement, array $options = []): int { unset($options['requestOptions']['transactionTag']); $session = $this->selectSession(SessionPoolInterface::CONTEXT_READWRITE); @@ -1906,6 +1923,7 @@ public function executePartitionedUpdate($statement, array $options = []) if (isset($options['transactionOptions']['excludeTxnFromChangeStreams'])) { $beginTransactionOptions['transactionOptions']['excludeTxnFromChangeStreams'] = $options['transactionOptions']['excludeTxnFromChangeStreams']; + unset($options['transactionOptions']); } $transaction = $this->operation->transaction($session, $beginTransactionOptions); @@ -2037,7 +2055,7 @@ public function executePartitionedUpdate($statement, array $options = []) * @codingStandardsIgnoreEnd * @return Result */ - public function read($table, KeySet $keySet, array $columns, array $options = []) + public function read($table, KeySet $keySet, array $columns, array $options = []): Result { unset($options['requestOptions']['transactionTag']); $session = $this->selectSession( @@ -2057,6 +2075,8 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] $options = $this->addLarHeader($options, true, $context); try { + // Unset the internal flag. + unset($options['singleUse']); return $this->operation->read($session, $table, $keySet, $columns, $options); } finally { $session->setExpiration(); @@ -2073,7 +2093,7 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] * * @return SessionPoolInterface|null */ - public function sessionPool() + public function sessionPool(): ?SessionPoolInterface { return $this->sessionPool; } @@ -2091,7 +2111,7 @@ public function sessionPool() * $database->close(); * ``` */ - public function close() + public function close(): void { if ($this->session) { if ($this->sessionPool) { @@ -2113,7 +2133,8 @@ public function __destruct() $this->close(); //@codingStandardsIgnoreStart //@codeCoverageIgnoreStart - } catch (\Exception $ex) {} + } catch (\Exception $ex) { + } //@codeCoverageIgnoreEnd //@codingStandardsIgnoreStart } @@ -2128,7 +2149,7 @@ public function __destruct() * @param array $options [optional] Configuration options. * @return Session */ - public function createSession(array $options = []) + public function createSession(array $options = []): Session { return $this->operation->createSession($this->name, $options); } @@ -2145,7 +2166,7 @@ public function createSession(array $options = []) * @param string $sessionName The session's name. * @return Session */ - public function session($sessionName) + public function session(string $sessionName): Session { return $this->operation->session($sessionName); } @@ -2156,7 +2177,7 @@ public function session($sessionName) * @access private * @return array */ - public function identity() + public function identity(): array { $databaseParts = explode('/', $this->name); $instanceParts = explode('/', $this->instance->name()); @@ -2169,33 +2190,196 @@ public function identity() } /** - * Returns the underlying connection. + * Creates a batch of sessions. + * + * @param array $options { + * @type array $sessionTemplate + * @type int $sessionCount + * } + */ + public function batchCreateSessions(array $options): array + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['database'] = $this->name; + + $request = $this->serializer->decodeMessage(new BatchCreateSessionsRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $response = $this->spannerClient->batchCreateSessions($request, $callOptions); + return $this->handleResponse($response); + } + + /** + * Delete session asynchronously. * * @access private - * @return ConnectionInterface + * @param array $options { + * @type name The session name to be deleted + * } + * @return PromiseInterface * @experimental */ - public function connection() + public function deleteSessionAsync(array $options): PromiseInterface { - return $this->connection; + [$data, $callOptions] = $this->splitOptionalArgs($options); + + $request = $this->serializer->decodeMessage(new DeleteSessionRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->spannerClient->deleteSessionAsync($request, $callOptions); } /** - * Represent the class in a more readable and digestable fashion. + * Lists backup operations. * - * @access private - * @codeCoverageIgnore + * @param array $options [optional] { + * Configuration options. + * + * @type int $pageSize + * The maximum number of resources contained in the underlying API + * response. The API may return fewer values in a page, even if + * there are additional values to be retrieved. + * @type int $resultLimit Limit the number of results returned in total. + * **Defaults to** `0` (return all results). + * @type string $pageToken + * A page token is used to specify a page of values to be returned. + * If no page token is specified (the default), the first page + * of values will be returned. Any page token used here must have + * been generated by a previous call to the API. + * } + * + * @return ItemIterator */ - public function __debugInfo() + public function backupOperations(array $options = []): ItemIterator { - return [ - 'connection' => get_class($this->connection), - 'projectId' => $this->projectId, - 'name' => $this->name, - 'instance' => $this->instance, - 'sessionPool' => $this->sessionPool, - 'session' => $this->session, + [$data, $callOptions] = $this->splitOptionalArgs($options); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + $request = $this->serializer->decodeMessage(new ListBackupOperationsRequest(), $data); + $request->setParent($this->instance->name()); + + return $this->buildLongRunningIterator( + [$this->databaseAdminClient, 'listBackupOperations'], + $request, + $callOptions + ); + } + + /** + * Create a database from a backup. + * + * @param string $name The database name. + * @param Backup|string $backup The backup to restore, given + * as a Backup instance or a string of the form + * `projects//instances//backups/`. + * @param array $options [optional] Configuration options. + * + * @return OperationResponse + */ + public function createDatabaseFromBackup($name, $backup, array $options = []): OperationResponse + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'parent' => $this->instance->name(), + 'databaseId' => $this->databaseIdOnly($name), + 'backup' => $backup instanceof Backup ? $backup->name() : $backup ]; + + $request = $this->serializer->decodeMessage(new RestoreDatabaseRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->databaseAdminClient->restoreDatabase($request, $callOptions) + ->withResultFunction($this->databaseResultFunction()); + } + + /** + * Lists database operations. + * + * @param array $options [optional] { + * Configuration options. + * + * @type int $pageSize + * The maximum number of resources contained in the underlying API + * response. The API may return fewer values in a page, even if + * there are additional values to be retrieved. + * @type int $resultLimit Limit the number of results returned in total. + * **Defaults to** `0` (return all results). + * @type string $pageToken + * A page token is used to specify a page of values to be returned. + * If no page token is specified (the default), the first page + * of values will be returned. Any page token used here must have + * been generated by a previous call to the API. + * } + * + * @return ItemIterator + */ + public function databaseOperations(array $options = []): ItemIterator + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + $request = $this->serializer->decodeMessage(new ListDatabaseOperationsRequest(), $data); + $request->setParent($this->instance->name()); + + return $this->buildLongRunningIterator( + [$this->databaseAdminClient, 'listDatabaseOperations'], + $request, + $callOptions + ); + } + + /** + * Resume a Long Running Operation + * + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return OperationResponse + */ + public function resumeOperation($operationName, array $options = []) + { + return (new OperationResponse( + $operationName, + $this->databaseAdminClient->getOperationsClient(), + $options + ))->withResultFunction($this->databaseResultFunction()); + } + + /** + * List long running operations. + * + * Example: + * ``` + * $operations = $backup->longRunningOperations(); + * ``` + * + * @param array $options [optional] { + * Configuration Options. + * + * @type string $name The name of the operation collection. + * @type string $filter The standard list filter. + * @type int $pageSize Maximum number of results to return per + * request. + * @type int $resultLimit Limit the number of results returned in total. + * **Defaults to** `0` (return all results). + * @type string $pageToken A previously-returned page token used to + * resume the loading of results from a specific point. + * } + * @return PagedListResponse + */ + public function longRunningOperations(array $options = []) + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); + $request->setName($this->name . '/operations'); + + return $this->buildLongRunningIterator( + [$this->databaseAdminClient->getOperationsClient(), 'listOperations'], + $request, + $callOptions + ); } /** @@ -2208,7 +2392,7 @@ public function __debugInfo() * @param array $options [optional] Configuration options. * @return Session */ - private function selectSession($context = SessionPoolInterface::CONTEXT_READ, array $options = []) + private function selectSession($context = SessionPoolInterface::CONTEXT_READ, array $options = []): Session { if ($this->session) { return $this->session; @@ -2241,7 +2425,7 @@ private function selectSession($context = SessionPoolInterface::CONTEXT_READ, ar * } * @return Timestamp The commit timestamp. */ - private function commitInSingleUseTransaction(array $mutations, array $options = []) + private function commitInSingleUseTransaction(array $mutations, array $options = []): Timestamp { unset($options['requestOptions']['transactionTag']); $options['mutations'] = $mutations; @@ -2258,12 +2442,12 @@ private function commitInSingleUseTransaction(array $mutations, array $options = * * @return string */ - private function fullyQualifiedDatabaseName($name) + private function fullyQualifiedDatabaseName($name): string { - $instance = InstanceAdminClient::parseName($this->instance->name())['instance']; + $instance = DatabaseAdminClient::parseName($this->instance->name())['instance']; try { - return GapicSpannerClient::databaseName( + return SpannerClient::databaseName( $this->projectId, $instance, $name @@ -2278,10 +2462,10 @@ private function fullyQualifiedDatabaseName($name) /** * Returns the 'CREATE DATABASE' statement as per the given database dialect * - * @param string $dialect The dialect of the database to be created + * @param int $dialect The dialect of the database to be created * @return string The specific 'CREATE DATABASE' statement */ - private function getCreateDbStatement($dialect) + private function getCreateDbStatement(int $dialect): string { $databaseId = DatabaseAdminClient::parseName($this->name())['database']; @@ -2291,4 +2475,148 @@ private function getCreateDbStatement($dialect) return sprintf('CREATE DATABASE `%s`', $databaseId); } + + /** + * Extracts a database id from fully qualified name. + * + * @param string $name The database name or id. + * @return string + */ + private function databaseIdOnly(string $name): string + { + try { + return DatabaseAdminClient::parseName($name)['database']; + } catch (ValidationException $e) { + return $name; + } + } + + private function parseMutations(array $rawMutations): array + { + if (!is_array($rawMutations)) { + return []; + } + + $mutations = []; + foreach ($rawMutations as $mutation) { + $type = array_keys($mutation)[0]; + $data = $mutation[$type]; + + switch ($type) { + case Operation::OP_DELETE: + $operation = $this->serializer->decodeMessage( + new Delete(), + $data + ); + break; + default: + $operation = new Write(); + $operation->setTable($data['table']); + $operation->setColumns($data['columns']); + + $modifiedData = []; + foreach ($data['values'] as $key => $param) { + $modifiedData[$key] = $this->fieldValue($param); + } + + $list = new ListValue(); + $list->setValues($modifiedData); + $values = [$list]; + $operation->setValues($values); + + break; + } + + $setterName = $this->mutationSetters[$type]; + $mutation = new Mutation(); + $mutation->$setterName($operation); + $mutations[] = $mutation; + } + return $mutations; + } + + /** + * @param mixed $param + * @return Value + */ + private function fieldValue($param): Value + { + $field = new Value(); + $value = $this->formatValueForApi($param); + + $setter = null; + switch (array_keys($value)[0]) { + case 'string_value': + $setter = 'setStringValue'; + break; + case 'number_value': + $setter = 'setNumberValue'; + break; + case 'bool_value': + $setter = 'setBoolValue'; + break; + case 'null_value': + $setter = 'setNullValue'; + break; + case 'struct_value': + $setter = 'setStructValue'; + $modifiedParams = []; + foreach ($param as $key => $value) { + $modifiedParams[$key] = $this->fieldValue($value); + } + $value = new Struct(); + $value->setFields($modifiedParams); + + break; + case 'list_value': + $setter = 'setListValue'; + $modifiedParams = []; + foreach ($param as $item) { + $modifiedParams[] = $this->fieldValue($item); + } + $list = new ListValue(); + $list->setValues($modifiedParams); + $value = $list; + + break; + } + + $value = is_array($value) ? current($value) : $value; + if ($setter) { + $field->$setter($value); + } + + return $field; + } + + private function databaseResultFunction(): Closure + { + return function (DatabaseProto $database): self { + $name = DatabaseAdminClient::parseName($database->getName()); + return $this->instance->database($name['database'], [ + 'sessionPool' => $this->sessionPool, + 'database' => $this->serializer->encodeMessage($database), + 'databaseRole' => $this->databaseRole, + ]); + }; + } + + /** + * Represent the class in a more readable and digestable fashion. + * + * @access private + * @codeCoverageIgnore + */ + public function __debugInfo() + { + return [ + 'spannerClient' => get_class($this->spannerClient), + 'databaseAdminClient' => get_class($this->databaseAdminClient), + 'projectId' => $this->projectId, + 'name' => $this->name, + 'instance' => $this->instance, + 'sessionPool' => $this->sessionPool, + 'session' => $this->session, + ]; + } } diff --git a/Spanner/src/Date.php b/Spanner/src/Date.php index 10a831995f1e..547ee2b82486 100644 --- a/Spanner/src/Date.php +++ b/Spanner/src/Date.php @@ -25,7 +25,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $date = $spanner->date(new \DateTimeImmutable('1995-02-04')); * ``` diff --git a/Spanner/src/Duration.php b/Spanner/src/Duration.php deleted file mode 100644 index 4fe506ac0880..000000000000 --- a/Spanner/src/Duration.php +++ /dev/null @@ -1,121 +0,0 @@ -duration($seconds, $nanoSeconds); - * ``` - * - * ``` - * // Duration objects can be cast to json-encoded strings. - * echo (string) $duration; - * ``` - */ -class Duration implements ValueInterface -{ - const TYPE = 'DURATION'; - - /** - * @var int - */ - private $seconds; - - /** - * @var int - */ - private $nanos; - - /** - * @param int $seconds The number of seconds in the duration. - * @param int $nanos The number of nanoseconds in the duration. - */ - public function __construct($seconds, $nanos = 0) - { - $this->seconds = $seconds; - $this->nanos = $nanos; - } - - /** - * Get the duration - * - * Example: - * ``` - * $res = $duration->get(); - * ``` - * - * @return array - */ - public function get() - { - return [ - 'seconds' => $this->seconds, - 'nanos' => $this->nanos - ]; - } - - /** - * Get the type. - * - * Example: - * ``` - * echo $duration->type(); - * ``` - * - * @return string - */ - public function type() - { - return self::TYPE; - } - - /** - * Format the value as a string. - * - * Example: - * ``` - * echo $duration->formatAsString(); - * ``` - * - * @return string - */ - public function formatAsString() - { - return json_encode($this->get()); - } - - /** - * Format the value as a string. - * - * @return string - * @access private - */ - public function __toString() - { - return $this->formatAsString(); - } -} diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index 2144a6fde12f..1b2617915fba 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -17,22 +17,29 @@ namespace Google\Cloud\Spanner; -use Google\ApiCore\ValidationException; -use Google\Cloud\Core\ArrayTrait; +use Closure; +use Google\ApiCore\ArrayTrait; +use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\Iam\Iam; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Iterator\PageIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; -use Google\Cloud\Core\LongRunning\LROTrait; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; +use Google\Cloud\Core\RequestHandler; +use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; use Google\Cloud\Spanner\Admin\Instance\V1\Instance\State; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Backup; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Spanner\Connection\IamInstance; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; use Google\Cloud\Spanner\Session\SessionPoolInterface; +use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\LongRunning\ListOperationsRequest; /** * Represents a Cloud Spanner instance @@ -41,50 +48,14 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => $projectId]); * * $instance = $spanner->instance('my-instance'); * ``` - * - * @method resumeOperation() { - * Resume a Long Running Operation - * - * Example: - * ``` - * $operation = $instance->resumeOperation($operationName); - * ``` - * - * @param string $operationName The Long Running Operation name. - * @param array $info [optional] The operation data. - * @return LongRunningOperation - * } - * @method longRunningOperations() { - * List long running operations. - * - * Example: - * ``` - * $operations = $instance->longRunningOperations(); - * ``` - * - * @param array $options [optional] { - * Configuration Options. - * - * @type string $name The name of the operation collection. - * @type string $filter The standard list filter. - * @type int $pageSize Maximum number of results to return per - * request. - * @type int $resultLimit Limit the number of results returned in total. - * **Defaults to** `0` (return all results). - * @type string $pageToken A previously-returned page token used to - * resume the loading of results from a specific point. - * } - * @return ItemIterator - * } */ class Instance { - use ArrayTrait; - use LROTrait; + use RequestTrait; const STATE_READY = State::READY; const STATE_CREATING = State::CREATING; @@ -92,50 +63,39 @@ class Instance const DEFAULT_NODE_COUNT = 1; /** - * @var ConnectionInterface - * @internal - */ - private $connection; - - /** - * @var string - */ - private $projectId; - - /** - * @var string + * @var IamManager|null */ - private $name; + private $iam; /** - * @var bool + * @var array */ - private $returnInt64AsObject; + private $directedReadOptions; /** * @var array */ - private $info; + private $defaultQueryOptions; /** - * @var Iam|null + * @var bool */ - private $iam; + private $routeToLeader; /** - * @var array + * @var string */ - private $directedReadOptions; + private $projectName; /** * Create an object representing a Cloud Spanner instance. * - * @param ConnectionInterface $connection The connection to the - * Cloud Spanner Admin API. This object is created by SpannerClient, - * and should not be instantiated outside of this client. - * @param LongRunningConnectionInterface $lroConnection An implementation - * mapping to methods which handle LRO resolution in the service. - * @param array $lroCallables + * @internal Instance is constructed by the {@see SpannerClient} class. + * + * @param GapicSpannerClient $spannerClient The spanner client. + * @param InstanceAdminClient $instanceAdminClient The instance admin client. + * @param DatabaseAdminClient $databaseAdminClient The database admin client. + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param string $projectId The project ID. * @param string $name The instance name or ID. * @param bool $returnInt64AsObject [optional] If true, 64 bit integers will be @@ -149,26 +109,26 @@ class Instance * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions} * If using the `replicaSelection::type` setting, utilize the constants available in * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type} to set a value. + * @type bool $routeToLeader Enable/disable Leader Aware Routing. + * **Defaults to** `true` (enabled). * } */ public function __construct( - ConnectionInterface $connection, - LongRunningConnectionInterface $lroConnection, - array $lroCallables, - $projectId, - $name, - $returnInt64AsObject = false, - array $info = [], + private GapicSpannerClient $spannerClient, + private InstanceAdminClient $instanceAdminClient, + private DatabaseAdminClient $databaseAdminClient, + private Serializer $serializer, + private string $projectId, + private string $name, + private bool $returnInt64AsObject = false, + private array $info = [], array $options = [] ) { - $this->connection = $connection; - $this->projectId = $projectId; $this->name = $this->fullyQualifiedInstanceName($name, $projectId); - $this->returnInt64AsObject = $returnInt64AsObject; - $this->info = $info; - - $this->setLroProperties($lroConnection, $lroCallables, $this->name); $this->directedReadOptions = $options['directedReadOptions'] ?? []; + $this->routeToLeader = $options['routeToLeader'] ?? true; + $this->defaultQueryOptions = $options['defaultQueryOptions'] ?? []; + $this->projectName = InstanceAdminClient::projectName($projectId); } /** @@ -234,15 +194,17 @@ public function info(array $options = []) */ public function exists(array $options = []) { + [$data, $callOptions] = $this->splitOptionalArgs($options); try { if ($this->info) { - $this->connection->getInstance([ + $data += [ 'name' => $this->name, - 'projectName' => InstanceAdminClient::projectName( - $this->projectId - ), - 'fieldMask' => ['name'], - ] + $options); + 'fieldMask' => ['paths' => ['name']], + ]; + $request = $this->serializer->decodeMessage(new GetInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); + + $this->instanceAdminClient->getInstance($request, $callOptions); } else { $this->reload($options); } @@ -276,12 +238,28 @@ public function exists(array $options = []) */ public function reload(array $options = []) { - $this->info = $this->connection->getInstance($options + [ - 'name' => $this->name, - 'projectName' => InstanceAdminClient::projectName($this->projectId), - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'name' => $this->name + ]; - return $this->info; + if (isset($data['fieldMask'])) { + $fieldMask = []; + if (is_array($data['fieldMask'])) { + foreach (array_values($data['fieldMask']) as $field) { + $fieldMask[] = $this->serializer::toSnakeCase($field); + } + } else { + $fieldMask[] = $this->serializer::toSnakeCase($data['fieldMask']); + } + $data['fieldMask'] = ['paths' => $fieldMask]; + } + + $request = $this->serializer->decodeMessage(new GetInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); + + $response = $this->instanceAdminClient->getInstance($request, $callOptions); + return $this->info = $this->handleResponse($response); } /** @@ -305,36 +283,34 @@ public function reload(array $options = []) * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://cloudplatform.googleblog.com/2015/10/using-labels-to-organize-Google-Cloud-Platform-resources.html). * } - * @return LongRunningOperation + * @return OperationResponse * @throws \InvalidArgumentException * @codingStandardsIgnoreEnd */ public function create(InstanceConfiguration $config, array $options = []) { + list($instance, $callOptions) = $this->splitOptionalArgs($options); $instanceId = InstanceAdminClient::parseName($this->name)['instance']; - $options += [ - 'displayName' => $instanceId, - 'labels' => [], - ]; - - if (isset($options['nodeCount']) && isset($options['processingUnits'])) { - throw new \InvalidArgumentException("Must only set either `nodeCount` or `processingUnits`"); + if (isset($instance['nodeCount']) && isset($instance['processingUnits'])) { + throw new \InvalidArgumentException('Must only set either `nodeCount` or `processingUnits`'); } - if (empty($options['nodeCount']) && empty($options['processingUnits'])) { - $options['nodeCount'] = self::DEFAULT_NODE_COUNT; + if (empty($instance['nodeCount']) && empty($instance['processingUnits'])) { + $instance['nodeCount'] = self::DEFAULT_NODE_COUNT; } - // This must always be set to CREATING, so overwrite anything else. - $options['state'] = State::CREATING; - - $operation = $this->connection->createInstance([ + $data = [ + 'parent' => InstanceAdminClient::projectName( + $this->projectId + ), 'instanceId' => $instanceId, - 'name' => $this->name, - 'projectName' => InstanceAdminClient::projectName($this->projectId), - 'config' => $config->name() - ] + $options); + 'instance' => $this->createInstanceArray($instance, $config) + ]; + + $request = $this->serializer->decodeMessage(new CreateInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->resumeOperation($operation['name'], $operation); + return $this->instanceAdminClient->createInstance($request, $callOptions) + ->withResultFunction($this->instanceResultFunction()); } /** @@ -360,7 +336,8 @@ public function state(array $options = []) { $info = $this->info($options); - return (isset($info['state'])) + // @TODO investigate why state is now 0 but in v1 it was unset + return (isset($info['state']) && $info['state'] !== 0) ? $info['state'] : null; } @@ -391,20 +368,28 @@ public function state(array $options = []) * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf). * } - * @return LongRunningOperation + * @return OperationResponse * @throws \InvalidArgumentException */ public function update(array $options = []) { + list($instance, $callOptions) = $this->splitOptionalArgs($options); + if (isset($options['nodeCount']) && isset($options['processingUnits'])) { - throw new \InvalidArgumentException("Must only set either `nodeCount` or `processingUnits`"); + throw new \InvalidArgumentException('Must only set either `nodeCount` or `processingUnits`'); } - $operation = $this->connection->updateInstance([ - 'name' => $this->name, - ] + $options); + $fieldMask = $this->fieldMask($instance); + $data = [ + 'fieldMask' => $fieldMask, + 'instance' => $this->createInstanceArray($instance) + ]; + + $request = $this->serializer->decodeMessage(new UpdateInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->resumeOperation($operation['name'], $operation); + return $this->instanceAdminClient->updateInstance($request, $callOptions) + ->withResultFunction($this->instanceResultFunction()); } /** @@ -424,9 +409,13 @@ public function update(array $options = []) */ public function delete(array $options = []) { - $this->connection->deleteInstance($options + [ - 'name' => $this->name - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['name'] = $this->name; + + $request = $this->serializer->decodeMessage(new DeleteInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $this->instanceAdminClient->deleteInstance($request, $callOptions); } /** @@ -449,7 +438,7 @@ public function delete(array $options = []) * @type SessionPoolInterface $sessionPool A pool used to manage * sessions. * } - * @return LongRunningOperation + * @return OperationResponse */ public function createDatabase($name, array $options = []) { @@ -473,22 +462,11 @@ public function createDatabase($name, array $options = []) * `projects//instances//backups/`. * @param array $options [optional] Configuration options. * - * @return LongRunningOperation + * @return OperationResponse */ - public function createDatabaseFromBackup($name, $backup, array $options = []) { - $backup = $backup instanceof Backup - ? $backup->name() - : $backup; - - $operation = $this->connection->restoreDatabase([ - 'instance' => $this->name(), - 'databaseId' => $this->databaseIdOnly($name), - 'backup' => $backup, - ] + $options); - - return $this->resumeOperation($operation['name'], $operation); + return $this->database($name)->createDatabaseFromBackup($name, $backup, $options); } /** @@ -518,16 +496,20 @@ public function createDatabaseFromBackup($name, $backup, array $options = []) public function database($name, array $options = []) { return new Database( - $this->connection, + $this->spannerClient, + $this->databaseAdminClient, + $this->serializer, $this, - $this->lroConnection, - $this->lroCallables, $this->projectId, $name, isset($options['sessionPool']) ? $options['sessionPool'] : null, $this->returnInt64AsObject, isset($options['database']) ? $options['database'] : [], - isset($options['databaseRole']) ? $options['databaseRole'] : '' + isset($options['databaseRole']) ? $options['databaseRole'] : '', + [ + 'routeToLeader' => $this->routeToLeader, + 'defaultQueryOptions' => $this->defaultQueryOptions, + ] ); } @@ -557,14 +539,27 @@ public function database($name, array $options = []) */ public function databases(array $options = []) { - $resultLimit = $this->pluck('resultLimit', $options, false); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['parent'] = $this->name; + + $resultLimit = $this->pluck('resultLimit', $data, false); return new ItemIterator( new PageIterator( function (array $database) { return $this->database($database['name'], ['database' => $database]); }, - [$this->connection, 'listDatabases'], - $options + ['instance' => $this->name], + function ($callOptions) use ($data) { + if (isset($callOptions['pageToken'])) { + $data['pageToken'] = $callOptions['pageToken']; + } + + $request = $this->serializer->decodeMessage(new ListDatabasesRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->listDatabases($request, $callOptions); + return $this->handleResponse($response); + }, + $callOptions, [ 'itemsKey' => 'databases', 'resultLimit' => $resultLimit @@ -588,10 +583,9 @@ function (array $database) { public function backup($name, array $backup = []) { return new Backup( - $this->connection, + $this->databaseAdminClient, + $this->serializer, $this, - $this->lroConnection, - $this->lroCallables, $this->projectId, $name, $backup @@ -631,6 +625,9 @@ public function backup($name, array $backup = []) */ public function backups(array $options = []) { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['parent'] = $this->name; + $resultLimit = $this->pluck('resultLimit', $options, false); return new ItemIterator( new PageIterator( @@ -640,8 +637,18 @@ function (array $backup) { $backup ); }, - [$this->connection, 'listBackups'], - $options + ['instance' => $this->name], + function ($callOptions) use ($data) { + if (isset($callOptions['pageToken'])) { + $data['pageToken'] = $callOptions['pageToken']; + } + + $request = $this->serializer->decodeMessage(new ListBackupsRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $response = $this->databaseAdminClient->listBackups($request, $callOptions); + return $this->handleResponse($response); + }, + $callOptions, [ 'itemsKey' => 'backups', 'resultLimit' => $resultLimit @@ -674,24 +681,11 @@ function (array $backup) { * been generated by a previous call to the API. * } * - * @return ItemIterator + * @return ItemIterator */ public function backupOperations(array $options = []) { - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $operation) { - return $this->resumeOperation($operation['name'], $operation); - }, - [$this->connection, 'listBackupOperations'], - $options + ['instance' => $this->name], - [ - 'itemsKey' => 'operations', - 'resultLimit' => $resultLimit - ] - ) - ); + return $this->database($this->name)->backupOperations($options); } /** @@ -718,24 +712,11 @@ function (array $operation) { * been generated by a previous call to the API. * } * - * @return ItemIterator + * @return ItemIterator */ public function databaseOperations(array $options = []) { - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $operation) { - return $this->resumeOperation($operation['name'], $operation); - }, - [$this->connection, 'listDatabaseOperations'], - $options + ['instance' => $this->name], - [ - 'itemsKey' => 'operations', - 'resultLimit' => $resultLimit - ] - ) - ); + return $this->database($this->name)->databaseOperations($options); } /** @@ -746,13 +727,15 @@ function (array $operation) { * $iam = $instance->iam(); * ``` * - * @return Iam + * @return IamManager */ public function iam() { if (!$this->iam) { - $this->iam = new Iam( - new IamInstance($this->connection), + $this->iam = new IamManager( + new RequestHandler($this->serializer, [$this->instanceAdminClient]), + $this->serializer, + InstanceAdminClient::class, $this->name ); } @@ -769,29 +752,10 @@ public function iam() */ private function fullyQualifiedInstanceName($name, $project) { - // try { - return InstanceAdminClient::instanceName( - $project, - $name - ); - // } catch (ValidationException $e) { - // return $name; - // } - } - - /** - * Extracts a database id from fully qualified name. - * - * @param string $name The database name or id. - * @return string - */ - private function databaseIdOnly($name) - { - try { - return DatabaseAdminClient::parseName($name)['database']; - } catch (ValidationException $e) { - return $name; - } + return InstanceAdminClient::instanceName( + $project, + $name + ); } /** @@ -803,7 +767,9 @@ private function databaseIdOnly($name) public function __debugInfo() { return [ - 'connection' => get_class($this->connection), + 'spannerClient' => get_class($this->spannerClient), + 'databaseAdminClient' => get_class($this->databaseAdminClient), + 'instanceAdminClient' => get_class($this->instanceAdminClient), 'projectId' => $this->projectId, 'name' => $this->name, 'info' => $this->info @@ -824,4 +790,109 @@ public function directedReadOptions() { return $this->directedReadOptions; } + + /** + * @param array $instanceArray + * @return array + */ + private function fieldMask(array $instanceArray) + { + $mask = []; + foreach (array_keys($instanceArray) as $key) { + $mask[] = $this->serializer::toSnakeCase($key); + } + return ['paths' => $mask]; + } + + /** + * @param array $instanceArray + * @param InstanceConfiguration $config + * @return array + */ + public function createInstanceArray(array $instanceArray, InstanceConfiguration $config = null) + { + return $instanceArray + [ + 'name' => $this->name, + 'displayName' => InstanceAdminClient::parseName($this->name)['instance'], + 'labels' => [], + 'config' => $config ? $config->name() : '' + ]; + } + + /** + * Resume a Long Running Operation + * + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return OperationResponse + */ + public function resumeOperation($operationName, array $options = []) + { + return (new OperationResponse( + $operationName, + $this->instanceAdminClient->getOperationsClient(), + $options + ))->withResultFunction($this->instanceResultFunction()); + } + + /** + * List long running operations. + * + * Example: + * ``` + * $operations = $backup->longRunningOperations(); + * ``` + * + * @param array $options [optional] { + * Configuration Options. + * + * @type string $name The name of the operation collection. + * @type string $filter The standard list filter. + * @type int $pageSize Maximum number of results to return per + * request. + * @type int $resultLimit Limit the number of results returned in total. + * **Defaults to** `0` (return all results). + * @type string $pageToken A previously-returned page token used to + * resume the loading of results from a specific point. + * } + * @return PagedListResponse + */ + public function longRunningOperations(array $options = []) + { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); + $request->setName($this->name . '/operations'); + + return $this->buildLongRunningIterator( + [$this->instanceAdminClient->getOperationsClient(), 'listOperations'], + $request, + $callOptions + ); + } + + private function instanceResultFunction(): Closure + { + return function (InstanceProto $result) { + $name = InstanceAdminClient::parseName($result->getName()); + return new self( + $this->spannerClient, + $this->instanceAdminClient, + $this->databaseAdminClient, + $this->serializer, + $this->projectId, + $name['instance'], + $this->returnInt64AsObject, + $this->serializer->encodeMessage($result), + [ + 'directedReadOptions' => $this->directedReadOptions, + 'routeToLeader' => $this->routeToLeader, + 'defaultQueryOptions' => $this->defaultQueryOptions, + ] + ); + }; + } } diff --git a/Spanner/src/InstanceConfiguration.php b/Spanner/src/InstanceConfiguration.php index e38162aad35f..4420e1ea1756 100644 --- a/Spanner/src/InstanceConfiguration.php +++ b/Spanner/src/InstanceConfiguration.php @@ -17,19 +17,22 @@ namespace Google\Cloud\Spanner; -use Google\Cloud\Core\ArrayTrait; -use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; -use Google\Cloud\Core\LongRunning\LROTrait; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Closure; +use Google\ApiCore\ApiException; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\ValidationException; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig\State; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig\Type; use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Spanner\Connection\LongRunningConnection; -use Google\ApiCore\ValidationException; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; +use Google\LongRunning\ListOperationsRequest; +use Google\Rpc\Code; /** * Represents a Cloud Spanner Instance Configuration. @@ -38,7 +41,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => $projectId]); * * $configuration = $spanner->instanceConfiguration('regional-europe-west'); * ``` @@ -49,78 +52,34 @@ */ class InstanceConfiguration { - use ArrayTrait; - use LROTrait; - - /** - * @var ConnectionInterface - * @internal - */ - private $connection; - - /** - * @var string - */ - private $projectId; + use RequestTrait; /** * @var string */ private $name; - /** - * @var array - */ - private $info; - /** * Create an instance configuration object. * - * @param ConnectionInterface $connection A service connection for the - * Spanner API. This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @internal InstanceConfiguration is constructed by the {@see SpannerClient} class. + * + * @param InstanceAdminClient The client library to use for the request + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param string $projectId The current project ID. * @param string $name The configuration name or ID. * @param array $info [optional] A service representation of the * configuration. - * @param LongRunningConnectionInterface $lroConnection An implementation - * mapping to methods which handle LRO resolution in the service. */ public function __construct( - ConnectionInterface $connection, - $projectId, + private InstanceAdminClient $instanceAdminClient, + private Serializer $serializer, + private string $projectId, $name, - array $info = [], - LongRunningConnectionInterface $lroConnection = null + private array $info = [] ) { - $this->connection = $connection; - $this->projectId = $projectId; $this->name = $this->fullyQualifiedConfigName($name, $projectId); $this->info = $info; - $lroConnection = $lroConnection ?: new LongRunningConnection($this->connection); - $instanceConfigFactoryFn = function ($instanceConfig) use ($connection, $projectId, $name, $lroConnection) { - $name = InstanceAdminClient::parseName($instanceConfig['name'])['instance_config']; - return new self( - $connection, - $projectId, - $name, - $instanceConfig, - $lroConnection - ); - }; - $this->setLroProperties( - $lroConnection, - [ - [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata', - 'callable' => $instanceConfigFactoryFn - ], - [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata', - 'callable' => $instanceConfigFactoryFn - ] - ] - ); } /** @@ -185,8 +144,11 @@ public function exists(array $options = []) { try { $this->reload($options = []); - } catch (NotFoundException $e) { - return false; + } catch (ApiException $e) { + if ($e->getCode() === Code::NOT_FOUND) { + return false; + } + throw $e; } return true; @@ -209,12 +171,19 @@ public function exists(array $options = []) */ public function reload(array $options = []) { - $this->info = $this->connection->getInstanceConfig($options + [ - 'name' => $this->name, - 'projectName' => InstanceAdminClient::projectName($this->projectId), - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += ['name' => $this->name]; + $callOptions = $this->addResourcePrefixHeader( + $callOptions, + InstanceAdminClient::projectName($this->projectId) + ); - return $this->info; + $response = $this->instanceAdminClient->getInstanceConfig( + $this->serializer->decodeMessage(new GetInstanceConfigRequest(), $data), + $callOptions + ); + + return $this->info = $this->handleResponse($response); } /** @@ -247,35 +216,43 @@ public function reload(array $options = []) * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return LongRunningOperation + * @return OperationResponse * @throws ValidationException * @codingStandardsIgnoreEnd */ public function create(InstanceConfiguration $baseConfig, array $replicas, array $options = []) { - $configId = InstanceAdminClient::parseName($this->name)['instance_config']; + [$data, $callOptions] = $this->splitOptionalArgs($options); + $leaderOptions = $baseConfig->__debugInfo()['info']['leaderOptions'] ?? []; - $options += [ - 'displayName' => $configId, - 'labels' => [], + $validateOnly = $data['validateOnly'] ?? false; + unset($data['validateOnly']); + $data += [ 'replicas' => $replicas, - 'leaderOptions' => $leaderOptions, + 'baseConfig' => $baseConfig->name(), + 'leaderOptions' => $leaderOptions + ]; + $instanceConfig = $this->instanceConfigArray($data); + $requestArray = [ + 'parent' => InstanceAdminClient::projectName($this->projectId), + 'instanceConfigId' => InstanceAdminClient::parseName($this->name)['instance_config'], + 'instanceConfig' => $instanceConfig, + 'validateOnly' => $validateOnly ]; - // Set output parameters to their default values. - $options['state'] = State::CREATING; - $options['configType'] = Type::USER_MANAGED; - $options['optionalReplicas'] = []; - $options['reconciling'] = false; + $request = $this->serializer->decodeMessage( + new CreateInstanceConfigRequest(), + $requestArray + ); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $operation = $this->connection->createInstanceConfig([ - 'instanceConfigId' => $configId, - 'name' => $this->name, - 'projectName' => InstanceAdminClient::projectName($this->projectId), - 'baseConfig' => $baseConfig->name(), - ] + $options); + $operationResponse = $this->instanceAdminClient->createInstanceConfig( + $request, + $callOptions + ); - return $this->resumeOperation($operation['name'], $operation); + return $operationResponse + ->withResultFunction($this->instanceConfigResultFunction()); } /** @@ -302,16 +279,30 @@ public function create(InstanceConfiguration $baseConfig, array $replicas, array * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return LongRunningOperation + * @return OperationResponse * @throws \InvalidArgumentException */ public function update(array $options = []) { - $operation = $this->connection->updateInstanceConfig([ - 'name' => $this->name, - ] + $options); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $validateOnly = $data['validateOnly'] ?? false; + unset($data['validateOnly']); + $data += ['name' => $this->name]; - return $this->resumeOperation($operation['name'], $operation); + $request = $this->serializer->decodeMessage(new UpdateInstanceConfigRequest(), [ + 'instanceConfig' => $data, + 'updateMask' => $this->fieldMask($data), + 'validateOnly' => $validateOnly + ]); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + $operationResponse = $this->instanceAdminClient->updateInstanceConfig( + $request, + $callOptions + ); + + return $operationResponse + ->withResultFunction($this->instanceConfigResultFunction()); } /** @@ -332,25 +323,33 @@ public function update(array $options = []) */ public function delete(array $options = []) { - $this->connection->deleteInstanceConfig([ - 'name' => $this->name - ] + $options); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += ['name' => $this->name]; + + $this->instanceAdminClient->deleteInstanceConfig( + $this->serializer->decodeMessage(new DeleteInstanceConfigRequest(), $data), + $this->addResourcePrefixHeader($callOptions, $this->name) + ); } /** - * A more readable representation of the object. + * Resume a Long Running Operation * - * @codeCoverageIgnore - * @access private + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return OperationResponse */ - public function __debugInfo() + public function resumeOperation($operationName, array $options = []) { - return [ - 'connection' => get_class($this->connection), - 'projectId' => $this->projectId, - 'name' => $this->name, - 'info' => $this->info, - ]; + return (new OperationResponse( + $operationName, + $this->instanceAdminClient->getOperationsClient(), + $options + ))->withResultFunction($this->instanceConfigResultFunction()); } /** @@ -371,4 +370,63 @@ private function fullyQualifiedConfigName($name, $projectId) return $name; } } + + /** + * @param array $args + * + * @return array + */ + private function instanceConfigArray(array $args) + { + $configId = InstanceAdminClient::parseName($this->name)['instance_config']; + + return $args += [ + 'name' => $this->name, + 'displayName' => $configId, + 'configType' => Type::USER_MANAGED + ]; + } + + /** + * @param array $instanceArray + * @return array + */ + private function fieldMask(array $instanceArray) + { + $mask = []; + foreach (array_keys($instanceArray) as $key) { + $mask[] = $this->serializer::toSnakeCase($key); + } + return ['paths' => $mask]; + } + + private function instanceConfigResultFunction(): Closure + { + return function (InstanceConfig $result) { + $name = InstanceAdminClient::parseName($result->getName()); + return new self( + $this->instanceAdminClient, + $this->serializer, + $this->projectId, + $name['instance_config'], + $this->serializer->encodeMessage($result) + ); + }; + } + + /** + * A more readable representation of the object. + * + * @codeCoverageIgnore + * @access private + */ + public function __debugInfo() + { + return [ + 'instanceAdminClient' => get_class($this->instanceAdminClient), + 'projectId' => $this->projectId, + 'name' => $this->name, + 'info' => $this->info, + ]; + } } diff --git a/Spanner/src/KeyRange.php b/Spanner/src/KeyRange.php index 727d33819191..c66614fbe91f 100644 --- a/Spanner/src/KeyRange.php +++ b/Spanner/src/KeyRange.php @@ -24,7 +24,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * // Create a KeyRange for all people named Bob, born in 1969. * $start = $spanner->date(new \DateTime('1969-01-01')); diff --git a/Spanner/src/KeySet.php b/Spanner/src/KeySet.php index ca11bceee36d..04a646e9c7dc 100644 --- a/Spanner/src/KeySet.php +++ b/Spanner/src/KeySet.php @@ -26,7 +26,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $keySet = $spanner->keySet(); * ``` @@ -102,7 +102,6 @@ public function ranges() return $this->ranges; } - /** * Add a single KeyRange. * diff --git a/Spanner/src/MutationTrait.php b/Spanner/src/MutationTrait.php index 51ca50c20789..14c5eaa56f78 100644 --- a/Spanner/src/MutationTrait.php +++ b/Spanner/src/MutationTrait.php @@ -17,9 +17,6 @@ namespace Google\Cloud\Spanner; -use Google\Cloud\Core\ArrayTrait; -use Google\ApiCore\ValidationException; - /** * Common helper methods used for creating array representation of * {@see \Google\Cloud\Spanner\V1\Mutation} @@ -28,8 +25,6 @@ */ trait MutationTrait { - use ArrayTrait; - /** * @var array */ @@ -334,6 +329,6 @@ private function flattenKeySet(KeySet $keySet) $keys['keys'] = $this->getValueMapper()->encodeValuesAsSimpleType($keys['keys'], true); } - return $this->arrayFilterRemoveNull($keys); + return array_filter($keys, fn ($v) => !is_null($v)); } } diff --git a/Spanner/src/Numeric.php b/Spanner/src/Numeric.php index 63d3a47ab170..f01c01ec32eb 100644 --- a/Spanner/src/Numeric.php +++ b/Spanner/src/Numeric.php @@ -29,7 +29,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $numeric = $spanner->numeric('99999999999999999999999999999999999999.999999999'); * ``` diff --git a/Spanner/src/Operation.php b/Spanner/src/Operation.php index 6c8aeecace91..96a215ab4fe8 100644 --- a/Spanner/src/Operation.php +++ b/Spanner/src/Operation.php @@ -17,14 +17,21 @@ namespace Google\Cloud\Spanner; -use Google\Cloud\Core\ArrayTrait; -use Google\Cloud\Core\TimeTrait; -use Google\Cloud\Core\ValidateTrait; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Batch\ReadPartition; -use Google\Cloud\Spanner\Connection\ConnectionInterface; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\V1\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionReadRequest; +use Google\Cloud\Spanner\V1\ReadRequest; +use Google\Cloud\Spanner\V1\RollbackRequest; +use Google\Cloud\Spanner\V1\Type; +use Google\Protobuf\Duration; use Google\Rpc\Code; use InvalidArgumentException; @@ -40,10 +47,8 @@ */ class Operation { - use ArrayTrait; + use RequestTrait; use MutationTrait; - use TimeTrait; - use ValidateTrait; const OP_INSERT = 'insert'; const OP_UPDATE = 'update'; @@ -52,28 +57,44 @@ class Operation const OP_DELETE = 'delete'; /** - * @var ConnectionInterface - * @internal + * @var ValueMapper */ - private $connection; + private $mapper; /** - * @var ValueMapper + * @var bool */ - private $mapper; + private $routeToLeader; + + /** + * @var array + */ + private $defaultQueryOptions; /** - * @param ConnectionInterface $connection A connection to Google Cloud - * Spanner. This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @param SpannerClient $spannerClient The Spanner client used to make requests. + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param bool $returnInt64AsObject If true, 64 bit integers will be * returned as a {@see \Google\Cloud\Core\Int64} object for 32 bit * platform compatibility. + * @param array $config [optional] { + * Configuration options. + * + * @type bool $routeToLeader Enable/disable Leader Aware Routing. + * **Defaults to** `true` (enabled). + * @type array $defaultQueryOptions + * } */ - public function __construct(ConnectionInterface $connection, $returnInt64AsObject) - { - $this->connection = $connection; + public function __construct( + private SpannerClient $spannerClient, + private Serializer $serializer, + bool $returnInt64AsObject, + $config = [] + ) { $this->mapper = new ValueMapper($returnInt64AsObject); + $this->routeToLeader = $this->pluck('routeToLeader', $config, false) ?: true; + $this->defaultQueryOptions = + $this->pluck('defaultQueryOptions', $config, false) ?: []; } /** @@ -132,18 +153,29 @@ public function commit(Session $session, array $mutations, array $options = []) */ public function commitWithResponse(Session $session, array $mutations, array $options = []) { - $options += [ - 'transactionId' => null + [$data, $callOptions] = $this->splitOptionalArgs($options); + $mutations = $this->serializeMutations($mutations); + $data += [ + 'transactionId' => null, + 'session' => $session->name(), + 'mutations' => $mutations ]; + $data = $this->formatSingleUseTransactionOptions($data); - $res = $this->connection->commit($this->arrayFilterRemoveNull([ - 'mutations' => $mutations, - 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session) - ]) + $options); + $request = $this->serializer->decodeMessage(new CommitRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $response = $this->spannerClient->commit($request, $callOptions); + $timestamp = $response->getCommitTimestamp(); - $time = $this->parseTimeString($res['commitTimestamp']); - return [new Timestamp($time[0], $time[1]), $res]; + return [ + new Timestamp( + $this->createDateTimeFromSeconds($timestamp->getSeconds()), + $timestamp->getNanos() + ), + $this->handleResponse($response) + ]; } /** @@ -162,11 +194,18 @@ public function rollback(Session $session, $transactionId, array $options = []) if (empty($transactionId)) { throw new InvalidArgumentException('Rollback failed: Transaction not initiated.'); } - $this->connection->rollback([ - 'transactionId' => $transactionId, + + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data = [ 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session) - ] + $options); + 'transactionId' => $transactionId + ]; + + $request = $this->serializer->decodeMessage(new RollbackRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $this->spannerClient->rollback($request, $callOptions); } /** @@ -221,7 +260,7 @@ public function execute(Session $session, $sql, array $options = []) $options['resumeToken'] = $resumeToken; } - return $this->connection->executeStreamingSql([ + return $this->executeStreamingSql([ 'sql' => $sql, 'session' => $session->name(), 'database' => $this->getDatabaseNameFromSession($session) @@ -261,7 +300,9 @@ public function executeUpdate( if (!isset($options['transaction']['begin'])) { $options['transaction'] = ['id' => $transaction->id()]; } + $statsItem = $this->pluck('statsItem', $options, false); $res = $this->execute($session, $sql, $options); + if (empty($transaction->id()) && $res->transaction()) { $transaction->setId($res->transaction()->id()); } @@ -276,7 +317,7 @@ public function executeUpdate( ); } - $statsItem = $options['statsItem'] ?? 'rowCountExact'; + $statsItem = $statsItem ?: 'rowCountExact'; return $stats[$statsItem]; } @@ -328,28 +369,19 @@ public function executeUpdateBatch( array $statements, array $options = [] ) { - $stmts = []; - foreach ($statements as $statement) { - if (!isset($statement['sql'])) { - throw new InvalidArgumentException('Each statement must contain a SQL key.'); - } - - $parameters = $this->pluck('parameters', $statement, false) ?: []; - $types = $this->pluck('types', $statement, false) ?: []; - $stmts[] = [ - 'sql' => $statement['sql'] - ] + $this->mapper->formatParamsForExecuteSql($parameters, $types); - } + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['transaction'] = $this->createTransactionSelector($data, $transaction->id()); + $data += [ + 'session' => $session->name(), + 'statements' => $this->formatStatements($statements) + ]; - if (!isset($options['transaction']['begin'])) { - $options['transaction'] = ['id' => $transaction->id()]; - } + $request = $this->serializer->decodeMessage(new ExecuteBatchDmlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $res = $this->connection->executeBatchDml([ - 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session), - 'statements' => $stmts - ] + $options); + $response = $this->spannerClient->executeBatchDml($request, $callOptions); + $res = $this->handleResponse($response); if (empty($transaction->id())) { // Get the transaction from array of ResultSets. @@ -402,14 +434,7 @@ public function read( array $columns, array $options = [] ) { - $options += [ - 'index' => null, - 'limit' => null, - 'offset' => null, - 'transactionContext' => null - ]; - - $context = $this->pluck('transactionContext', $options); + $context = $this->pluck('transactionContext', $options, false); $call = function ($resumeToken = null, $transaction = null) use ( $table, @@ -425,7 +450,7 @@ public function read( $options['resumeToken'] = $resumeToken; } - return $this->connection->streamingRead([ + return $this->streamingRead([ 'table' => $table, 'session' => $session->name(), 'columns' => $columns, @@ -462,10 +487,11 @@ public function transaction(Session $session, array $options = []) { $options += [ 'singleUse' => false, - 'isRetry' => false, 'requestOptions' => [] ]; + $isRetry = $this->pluck('isRetry', $options, false) ?: false; $transactionTag = $this->pluck('tag', $options, false); + if (isset($transactionTag)) { $options['requestOptions']['transactionTag'] = $transactionTag; } @@ -473,6 +499,10 @@ public function transaction(Session $session, array $options = []) if (!$options['singleUse'] && (!isset($options['begin']) || isset($options['transactionOptions']['partitionedDml'])) ) { + // Single use transactions never calls the beginTransaction API. + // The `singleUse` key creates issue with serializer as BeginTransactionRequest + // does not have this attribute. + unset($options['singleUse']); $res = $this->beginTransaction($session, $options); } else { $res = []; @@ -483,7 +513,7 @@ public function transaction(Session $session, array $options = []) $res, [ 'tag' => $transactionTag, - 'isRetry' => $options['isRetry'], + 'isRetry' => $isRetry, 'transactionOptions' => $options ] ); @@ -506,7 +536,7 @@ public function createTransaction(Session $session, array $res = [], array $opti ]; $options += [ 'tag' => null, - 'transactionOptions' => null + 'transactionOptions' => [] ]; $options['isRetry'] = $options['isRetry'] ?? false; @@ -538,10 +568,6 @@ public function createTransaction(Session $session, array $res = [], array $opti * @type string $className If set, an instance of the given class will * be instantiated. This setting is intended for internal use. * **Defaults to** `Google\Cloud\Spanner\Snapshot`. - * @type array $directedReadOptions Directed read options. - * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions} - * If using the `replicaSelection::type` setting, utilize the constants available in - * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type} to set a value. * } * @return mixed */ @@ -551,14 +577,18 @@ public function snapshot(Session $session, array $options = []) 'singleUse' => false, 'className' => Snapshot::class ]; + $className = $this->pluck('className', $options); if (!$options['singleUse']) { + // Single use transactions never calls the beginTransaction API. + // The `singleUse` key creates issue with serializer as BeginTransactionRequest + // does not have this attribute. + unset($options['singleUse']); $res = $this->beginTransaction($session, $options); } else { $res = []; } - $className = $this->pluck('className', $options); return $this->createSnapshot( $session, $res + $options, @@ -617,13 +647,21 @@ public function createSnapshot(Session $session, array $res = [], $className = S */ public function createSession($databaseName, array $options = []) { - $res = $this->connection->createSession([ + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data = [ 'database' => $databaseName, 'session' => [ 'labels' => $this->pluck('labels', $options, false) ?: [], 'creator_role' => $this->pluck('creator_role', $options, false) ?: '' - ] - ] + $options); + ]]; + + $request = $this->serializer->decodeMessage(new CreateSessionRequest(), $data); + + $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $response = $this->spannerClient->createSession($request, $callOptions); + $res = $this->handleResponse($response); return $this->session($res['name']); } @@ -641,13 +679,15 @@ public function createSession($databaseName, array $options = []) */ public function session($sessionName) { - $sessionNameComponents = GapicSpannerClient::parseName($sessionName); + $sessionNameComponents = SpannerClient::parseName($sessionName); return new Session( - $this->connection, + $this->spannerClient, + $this->serializer, $sessionNameComponents['project'], $sessionNameComponents['instance'], $sessionNameComponents['database'], - $sessionNameComponents['session'] + $sessionNameComponents['session'], + ['routeToLeader' => $this->routeToLeader] ); } @@ -692,19 +732,22 @@ public function partitionQuery(Session $session, $transactionId, $sql, array $op { // cache this to pass to the partition instance. $originalOptions = $options; + [$data, $callOptions] = $this->splitOptionalArgs($options); - $parameters = $this->pluck('parameters', $options, false) ?: []; - $types = $this->pluck('types', $options, false) ?: []; - $options += $this->mapper->formatParamsForExecuteSql($parameters, $types); + $data = $this->formatPartitionQueryOptions($data); + $data += [ + 'transaction' => $this->createTransactionSelector($data, $transactionId), + 'session' => $session->name(), + 'sql' => $sql, + 'partitionOptions' => $this->partitionOptions($data) + ]; - $options = $this->partitionOptions($options); + $request = $this->serializer->decodeMessage(new PartitionQueryRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $res = $this->connection->partitionQuery([ - 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session), - 'transactionId' => $transactionId, - 'sql' => $sql - ] + $options); + $response = $this->spannerClient->partitionQuery($request, $callOptions); + $res = $this->handleResponse($response); $partitions = []; foreach ($res['partitions'] as $partition) { @@ -752,17 +795,23 @@ public function partitionRead( ) { // cache this to pass to the partition instance. $originalOptions = $options; - - $options = $this->partitionOptions($options); - - $res = $this->connection->partitionRead([ + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'transaction' => $this->createTransactionSelector($data, $transactionId), 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session), - 'transactionId' => $transactionId, 'table' => $table, 'columns' => $columns, - 'keySet' => $this->flattenKeySet($keySet) - ] + $options); + // 'keySet' => $this->formatKeySet($this->flattenKeySet($keySet)), + 'keySet' => $this->flattenKeySet($keySet), + 'partitionOptions' => $this->partitionOptions($data) + ]; + + $request = $this->serializer->decodeMessage(new PartitionReadRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + + $response = $this->spannerClient->partitionRead($request, $callOptions); + $res = $this->handleResponse($response); $partitions = []; foreach ($res['partitions'] as $partition) { @@ -784,14 +833,12 @@ public function partitionRead( * @param array $options * @return array */ - private function partitionOptions(array $options) + private function partitionOptions(array &$options) { - $options['partitionOptions'] = array_filter([ + return array_filter([ 'partitionSizeBytes' => $this->pluck('partitionSizeBytes', $options, false), 'maxPartitions' => $this->pluck('maxPartitions', $options, false) ]); - - return $options; } /** @@ -806,14 +853,24 @@ private function partitionOptions(array $options) */ private function beginTransaction(Session $session, array $options = []) { - $options += [ - 'transactionOptions' => [] + [$data, $callOptions] = $this->splitOptionalArgs($options); + $transactionOptions = $this->formatTransactionOptions( + $this->pluck('transactionOptions', $data, false) ?: [] + ); + if (isset($transactionOptions['readWrite']) + || isset($transactionOptions['partitionedDml'])) { + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); + } + $data += [ + 'session' => $session->name(), + 'options' => $transactionOptions ]; - return $this->connection->beginTransaction($options + [ - 'session' => $session->name(), - 'database' => $this->getDatabaseNameFromSession($session) - ]); + $request = $this->serializer->decodeMessage(new BeginTransactionRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + + $response = $this->spannerClient->beginTransaction($request, $callOptions); + return $this->handleResponse($response); } /** @@ -848,6 +905,238 @@ private function getDatabaseNameFromSession(Session $session) return $session->info()['databaseName']; } + /** + * Serialize the mutations. + * + * @param array $mutations + * @return array + */ + private function serializeMutations(array $mutations) + { + $serializedMutations = []; + if (is_array($mutations)) { + foreach ($mutations as $mutation) { + $type = array_keys($mutation)[0]; + $data = $mutation[$type]; + + switch ($type) { + case Operation::OP_DELETE: + // if (isset($data['keySet'])) { + // $data['keySet'] = $this->formatKeySet($data['keySet']); + // } + break; + default: + $modifiedData = array_map([$this, 'formatValueForApi'], $data['values']); + $data['values'] = [['values' => $modifiedData]]; + + break; + } + + $serializedMutations[] = [$type => $data]; + } + } + + return $serializedMutations; + } + + /** + * Format statements. + * + * @param array $statements + * @return array + */ + private function formatStatements(array $statements) + { + $result = []; + foreach ($statements as $statement) { + if (!isset($statement['sql'])) { + throw new InvalidArgumentException('Each statement must contain a SQL key.'); + } + + $parameters = $this->pluck('parameters', $statement, false) ?: []; + $types = $this->pluck('types', $statement, false) ?: []; + $mappedStatement = [ + 'sql' => $statement['sql'] + ] + $this->mapper->formatParamsForExecuteSql($parameters, $types); + + $result[] = $this->formatSqlParams($mappedStatement); + } + return $result; + } + + /** + * @param array $args + * @return array + */ + private function formatSqlParams(array $args) + { + $params = $this->pluck('params', $args); + if ($params) { + $modifiedParams = array_map([$this, 'formatValueForApi'], $params); + $args['params'] = ['fields' => $modifiedParams]; + } + + return $args; + } + + /** + * @param array $args + * @param ?string $transactionId + * + * @return array + */ + private function createTransactionSelector(array &$args, ?string $transactionId = null) + { + $transactionSelector = []; + if (isset($args['transaction'])) { + $transactionSelector = $this->pluck('transaction', $args); + + if (isset($transactionSelector['singleUse'])) { + $transactionSelector['singleUse'] = + $this->formatTransactionOptions($transactionSelector['singleUse']); + } + + if (isset($transactionSelector['begin'])) { + $transactionSelector['begin'] = + $this->formatTransactionOptions($transactionSelector['begin']); + } + } elseif ($transactionId) { + $transactionSelector = ['id' => $transactionId]; + } + + return $transactionSelector; + } + + /** + * @param array $data + * + * @return array + */ + private function createQueryOptions(array $args) + { + $queryOptions = $this->pluck('queryOptions', $args, false) ?: []; + // Query options precedence is query-level, then environment-level, then client-level. + $envQueryOptimizerVersion = getenv('SPANNER_OPTIMIZER_VERSION'); + $envQueryOptimizerStatisticsPackage = getenv('SPANNER_OPTIMIZER_STATISTICS_PACKAGE'); + if (!empty($envQueryOptimizerVersion)) { + $queryOptions += ['optimizerVersion' => $envQueryOptimizerVersion]; + } + if (!empty($envQueryOptimizerStatisticsPackage)) { + $queryOptions += ['optimizerStatisticsPackage' => $envQueryOptimizerStatisticsPackage]; + } + $queryOptions += $this->defaultQueryOptions ?: []; + return $queryOptions; + } + + /** + * @param array $transactionOptions + * @return array + */ + private function formatTransactionOptions(array $transactionOptions) + { + if (isset($transactionOptions['readOnly'])) { + $ro = $transactionOptions['readOnly']; + if (isset($ro['minReadTimestamp'])) { + $ro['minReadTimestamp'] = $this->formatTimestampForApi($ro['minReadTimestamp']); + } + + if (isset($ro['readTimestamp'])) { + $ro['readTimestamp'] = $this->formatTimestampForApi($ro['readTimestamp']); + } + + $transactionOptions['readOnly'] = $ro; + } + + return $transactionOptions; + } + + /** + * @param array $args + * @return \Generator + */ + private function executeStreamingSql(array $args) + { + list($data, $callOptions) = $this->splitOptionalArgs($args); + $data = $this->formatSqlParams($data); + $data['transaction'] = $this->createTransactionSelector($data); + $data['queryOptions'] = $this->createQueryOptions($data); + $callOptions = $this->conditionallyUnsetLarHeader($callOptions, $this->routeToLeader); + $databaseName = $this->pluck('database', $data); + + $request = $this->serializer->decodeMessage(new ExecuteSqlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); + + $response = $this->spannerClient->executeStreamingSql($request, $callOptions); + return $this->handleResponse($response); + } + + /** + * @param array $args + * @return \Generator + */ + private function streamingRead(array $args) + { + list($data, $callOptions) = $this->splitOptionalArgs($args); + // $data['keySet'] = ($this->pluck('keySet', $data); + $data['transaction'] = $this->createTransactionSelector($data); + $callOptions = $this->conditionallyUnsetLarHeader($callOptions, $this->routeToLeader); + $databaseName = $this->pluck('database', $data); + + $request = $this->serializer->decodeMessage(new ReadRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); + + $response = $this->spannerClient->streamingRead($request, $callOptions); + return $this->handleResponse($response); + } + + /** + * @param array $args + * @return array + */ + private function formatSingleUseTransactionOptions(array $args) + { + // Internal flag, need to unset before passing to serializer + unset($args['singleUse']); + if (isset($args['singleUseTransaction'])) { + $args['singleUseTransaction'] = ['readWrite' => []]; + // request ignores singleUseTransaction even if the transactionId is set to null + unset($args['transactionId']); + } + return $args; + } + + /** + * @param array $args + * @param string $transactionId + * + * @return array + */ + private function formatPartitionQueryOptions(array $args) + { + $parameters = $this->pluck('parameters', $args, false) ?: []; + $types = $this->pluck('types', $args, false) ?: []; + $args += $this->mapper->formatParamsForExecuteSql($parameters, $types); + $args = $this->formatSqlParams($args); + return $args; + } + + /** + * Conditionally unset the LAR header. + * + * @param array $args Request arguments. + * @param bool $value Whether to set or unset the LAR header. + * @return array + */ + private function conditionallyUnsetLarHeader( + array $args, + bool $value = true + ) { + if (!$value) { + unset($args['headers'][$this->larHeader]); + } + return $args; + } + /** * Represent the class in a more readable and digestable fashion. * @@ -857,7 +1146,7 @@ private function getDatabaseNameFromSession(Session $session) public function __debugInfo() { return [ - 'connection' => get_class($this->connection), + 'spannerClient' => get_class($this->spannerClient), ]; } } diff --git a/Spanner/src/PgJsonb.php b/Spanner/src/PgJsonb.php index 5788e7dbe8e4..6118eb2bdae7 100644 --- a/Spanner/src/PgJsonb.php +++ b/Spanner/src/PgJsonb.php @@ -30,7 +30,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $pgJsonb = $spanner->pgJsonb('{}'); * ``` */ diff --git a/Spanner/src/PgNumeric.php b/Spanner/src/PgNumeric.php index 8128d3208502..4bc820ca9c35 100644 --- a/Spanner/src/PgNumeric.php +++ b/Spanner/src/PgNumeric.php @@ -31,7 +31,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $pgNumeric = $spanner->pgNumeric('99999999999999999999999999999999999999.000000999999999'); * ``` diff --git a/Spanner/src/PgOid.php b/Spanner/src/PgOid.php index 8a3d0b9a1ced..047474e1151f 100644 --- a/Spanner/src/PgOid.php +++ b/Spanner/src/PgOid.php @@ -28,7 +28,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $pgOid = $spanner->pgOid('123'); * ``` */ diff --git a/Spanner/src/RequestHeaderTrait.php b/Spanner/src/RequestHeaderTrait.php deleted file mode 100644 index 456402c38e1c..000000000000 --- a/Spanner/src/RequestHeaderTrait.php +++ /dev/null @@ -1,84 +0,0 @@ -larHeader] = ['true']; + } + return $args; + } + + /** + * Add the `google-cloud-resource-prefix` header value to the request. + * + * @param array $args Request arguments. + * @param string $value Resource prefix header value. + * @return array + */ + private function addResourcePrefixHeader(array $args, string $value) + { + $args['headers'][$this->resourcePrefixHeader] = [$value]; + return $args; + } + + /** + * Helper making list calls for long running operations. + * + * + * @param callable $call The GAPIC client and method for the list operations request + * @param Message $request The list operations request + * @param array $callOptions [optional] Call options for the request + * @param callable $operationResponseMapper [optional] A callable to map the Operation to an + * operation response. Defaults to `$this->resumeOperation()`. + * @return ItemIterator + */ + private function buildLongRunningIterator( + callable $call, + Message $request, + array $callOptions = [], + ?callable $operationResponseMapper = null + ): ItemIterator { + $resultLimit = $this->pluck('resultLimit', $callOptions, false) ?: 0; + return new ItemIterator( + new PageIterator( + $operationResponseMapper ?: function (Operation $operation) { + return $this->resumeOperation( + $operation->getName(), + ['lastProtoResponse' => $operation] + ); + }, + function (array $args) use ($call) { + if ($pageToken = $this->pluck('pageToken', $args, false) ?: null) { + $args['request']->setPageToken($pageToken); + } + try { + $page = $call($args['request'], $args['callOptions'])->getPage(); + } catch (ApiException $e) { + throw $this->convertToGoogleException($e); + } + return [ + 'operations' => iterator_to_array($page->getResponseObject()->getOperations()), + 'nextResultToken' => $page->getNextPageToken(), + ]; + }, [ + 'request' => $request, + 'callOptions' => $callOptions + ], [ + 'itemsKey' => 'operations', + 'resultLimit' => $resultLimit + ] + ) + ); + } +} diff --git a/Spanner/src/Result.php b/Spanner/src/Result.php index b335804a2331..c15b6fff0901 100644 --- a/Spanner/src/Result.php +++ b/Spanner/src/Result.php @@ -17,6 +17,7 @@ namespace Google\Cloud\Spanner; +use Google\ApiCore\RetrySettings; use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Core\ExponentialBackoff; use Google\Cloud\Spanner\Session\Session; @@ -31,7 +32,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $database = $spanner->connect('my-instance', 'my-database'); * $result = $database->execute('SELECT * FROM Posts'); @@ -132,8 +133,12 @@ class Result implements \IteratorAggregate * @param callable $call A callable, yielding a generator filled with results. * @param string $transactionContext The transaction's context. * @param ValueMapper $mapper Maps values. - * @param int $retries Number of attempts to resume a broken stream, assuming - * a resume token is present. **Defaults to** 3. + * @param ?RetrySettings $retrySettings { + * Retry configuration options. Currently, only the `maxRetries` option is supported. + * + * @type int $maxRetries The maximum number of retry attempts before the operation fails. + * Defaults to 3. + * } */ public function __construct( Operation $operation, @@ -141,14 +146,14 @@ public function __construct( callable $call, $transactionContext, ValueMapper $mapper, - $retries = 3 + ?RetrySettings $retrySettings = null ) { $this->operation = $operation; $this->session = $session; $this->call = $call; $this->transactionContext = $transactionContext; $this->mapper = $mapper; - $this->retries = $retries; + $this->retries = isset($retrySettings) ? $retrySettings->getMaxRetries() : 3; $this->createGenerator(); } @@ -178,7 +183,7 @@ public function __construct( * @throws \RuntimeException When duplicate column names exist with a * selected format of `Result::RETURN_ASSOCIATIVE`. */ - public function rows($format = self::RETURN_ASSOCIATIVE) + public function rows($format = self::RETURN_ASSOCIATIVE): \Generator { $bufferedResults = []; $call = $this->call; diff --git a/Spanner/src/Serializer.php b/Spanner/src/Serializer.php new file mode 100644 index 000000000000..4b1ef0dd3f78 --- /dev/null +++ b/Spanner/src/Serializer.php @@ -0,0 +1,99 @@ + function ($v) { + return $this->flattenValue($v); + }, + 'google.protobuf.ListValue' => function ($v) { + return $this->flattenListValue($v); + }, + 'google.protobuf.Struct' => function ($v) { + return $this->flattenStruct($v); + }, + 'google.protobuf.Timestamp' => function ($v) { + return $this->formatTimestampFromApi($v); + } + ]; + $decodeFieldTransformers = []; + $decodeMessageTypeTransformers = [ + 'google.spanner.v1.KeySet' => function ($keySet) { + $keys = $this->pluck('keys', $keySet, false); + if ($keys) { + $keySet['keys'] = array_map( + fn ($key) => $this->formatListForApi((array) $key), + $keys + ); + } + + if (isset($keySet['ranges'])) { + $keySet['ranges'] = array_map(function ($rangeItem) { + return array_map([$this, 'formatListForApi'], $rangeItem); + }, $keySet['ranges']); + + if (empty($keySet['ranges'])) { + unset($keySet['ranges']); + } + } + + return $keySet; + } + ]; + $customEncoders = [ + // A custom encoder that short-circuits the encodeMessage in Serializer class, + // but only if the argument is of the type PartialResultSet. + PartialResultSet::class => function ($msg) { + $data = json_decode($msg->serializeToJsonString(), true); + + // We only override metadata fields, if it actually exists in the response. + // This is specially important for large data sets which is received in chunks. + // Metadata is only received in the first 'chunk' and we don't want to set empty metadata fields + // when metadata was not returned from the server. + if (isset($data['metadata'])) { + // The transaction id is serialized as a base64 encoded string in $data. So, we + // add a step to get the transaction id using a getter instead of the serialized value. + // The null-safe operator is used to handle edge cases where the relevant fields are not present. + $data['metadata']['transaction']['id'] = (string) $msg?->getMetadata()?->getTransaction()?->getId(); + + // Helps convert metadata enum values from string types to their respective code/annotation + // pairs. Ex: INT64 is converted to {code: 2, typeAnnotation: 0}. + $fields = $msg->getMetadata()?->getRowType()?->getFields(); + $data['metadata']['rowType']['fields'] = $this->getFieldDataFromRepeatedFields($fields); + } + + // These fields in stats should be an int + if (isset($data['stats']['rowCountLowerBound'])) { + $data['stats']['rowCountLowerBound'] = (int) $data['stats']['rowCountLowerBound']; + } + if (isset($data['stats']['rowCountExact'])) { + $data['stats']['rowCountExact'] = (int) $data['stats']['rowCountExact']; + } + + return $data; + } + ]; + + parent::__construct( + $fieldTransformers, + $messageTypeTransformers, + $decodeFieldTransformers, + $decodeMessageTypeTransformers, + $customEncoders, + ); + } +} \ No newline at end of file diff --git a/Spanner/src/Session/CacheSessionPool.php b/Spanner/src/Session/CacheSessionPool.php index ec4a04c8e210..73f953bfaad2 100644 --- a/Spanner/src/Session/CacheSessionPool.php +++ b/Spanner/src/Session/CacheSessionPool.php @@ -83,7 +83,7 @@ * use Google\Cloud\Spanner\Session\CacheSessionPool; * use Symfony\Component\Cache\Adapter\FilesystemAdapter; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $cache = new FilesystemAdapter(); * $sessionPool = new CacheSessionPool($cache); * @@ -111,7 +111,7 @@ * use Google\Cloud\Spanner\Session\CacheSessionPool; * use Symfony\Component\Cache\Adapter\FilesystemAdapter; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $cache = new FilesystemAdapter(); * $sessionPool = new CacheSessionPool($cache, [ * 'databaseRole' => 'Reader' @@ -127,7 +127,7 @@ class CacheSessionPool implements SessionPoolInterface use SysvTrait; const CACHE_KEY_TEMPLATE = 'cache-session-pool.%s.%s.%s'; - const DURATION_SESSION_LIFETIME = 28*24*3600; // 28 days + const DURATION_SESSION_LIFETIME = 28 * 24 * 3600; // 28 days const DURATION_TWENTY_MINUTES = 1200; const DURATION_ONE_MINUTE = 60; const WINDOW_SIZE = 600; @@ -232,7 +232,7 @@ public function acquire($context = SessionPoolInterface::CONTEXT_READ) { // Try to get a session, run maintenance on the pool, and calculate if // we need to create any new sessions. - list($session, $toCreate) = $this->config['lock']->synchronize(function () { + [$session, $toCreate] = $this->config['lock']->synchronize(function () { $toCreate = []; $session = null; $shouldSave = false; @@ -471,7 +471,7 @@ public function warmup() } $exception = null; - list ($createdSessions, $exception) = $this->createSessions(count($toCreate)); + list($createdSessions, $exception) = $this->createSessions(count($toCreate)); $this->config['lock']->synchronize(function () use ($toCreate, $createdSessions) { $item = $this->cacheItemPool->getItem($this->cacheKey); @@ -690,8 +690,7 @@ private function createSessions($count) // @see https://github.com/googleapis/google-cloud-php/pull/2342#discussion_r327925546 while ($count > $created) { try { - $res = $this->database->connection()->batchCreateSessions([ - 'database' => $this->database->name(), + $res = $this->database->batchCreateSessions([ 'sessionTemplate' => [ 'labels' => isset($this->config['labels']) ? $this->config['labels'] : [], 'creator_role' => isset($this->config['databaseRole']) ? $this->config['databaseRole'] : '' @@ -873,11 +872,9 @@ private function deleteSessions(array $sessions, $waitForPromises = false) { $this->deleteCalls = []; foreach ($sessions as $session) { - $this->deleteCalls[] = $this->database->connection() - ->deleteSessionAsync([ - 'name' => $session['name'], - 'database' => $this->database->name() - ]); + $this->deleteCalls[] = $this->database->deleteSessionAsync([ + 'name' => $session['name'] + ]); } if ($waitForPromises && !empty($this->deleteCalls)) { @@ -1035,7 +1032,7 @@ public function maintain() $maintainInterval = $now - $prevMaintainTime; $maxLifetime = self::SESSION_EXPIRATION_SECONDS - 600; $totalSessionsCount = min($totalSessionsCount, $maintainedSessionsCount); - $meanRefreshCount = (int)($totalSessionsCount * $maintainInterval / $maxLifetime); + $meanRefreshCount = (int) ($totalSessionsCount * $maintainInterval / $maxLifetime); $meanRefreshCount = min($meanRefreshCount, $maintainedSessionsCount); // There may be sessions already refreshed since previous maintenance, // so we can save some refresh requests. diff --git a/Spanner/src/Session/Session.php b/Spanner/src/Session/Session.php index be909d6c7892..1985059f644e 100644 --- a/Spanner/src/Session/Session.php +++ b/Spanner/src/Session/Session.php @@ -17,35 +17,32 @@ namespace Google\Cloud\Spanner\Session; +use Google\ApiCore\ArrayTrait; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Spanner\V1\SpannerClient; +use Google\Cloud\Spanner\Database; +use Google\Cloud\Spanner\RequestTrait; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\GetSessionRequest; /** * Represents and manages a single Cloud Spanner session. */ class Session { - /** - * @var ConnectionInterface - * @internal - */ - private $connection; + use RequestTrait; /** - * @var string - */ - private $projectId; - - /** - * @var string + * @var int|null */ - private $instance; + private $expiration; /** - * @var string + * @var bool */ - private $database; + private $routeToLeader; /** * @var string @@ -53,35 +50,29 @@ class Session private $databaseName; /** - * @var string - */ - private $name; - - /** - * @var int|null - */ - private $expiration; - - /** - * @param ConnectionInterface $connection A connection to Cloud Spanner. - * This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @internal Session is constructed by the {@see Database} class. + * + * @param Serializer $serializer The serializer instance to encode/decode messages. * @param string $projectId The project ID. * @param string $instance The instance name. * @param string $database The database name. * @param string $name The session name. + * @param array $config [optional] { + * Configuration options. + * + * @type bool $routeToLeader Enable/disable Leader Aware Routing. + * **Defaults to** `true` (enabled). + * } */ public function __construct( - ConnectionInterface $connection, - $projectId, - $instance, - $database, - $name + private SpannerClient $spannerClient, + private Serializer $serializer, + private $projectId, + private $instance, + private $database, + private $name, + $config = [] ) { - $this->connection = $connection; - $this->projectId = $projectId; - $this->instance = $instance; - $this->database = $database; $this->databaseName = SpannerClient::databaseName( $projectId, $instance, @@ -93,6 +84,7 @@ public function __construct( $database, $name ); + $this->routeToLeader = $this->pluck('routeToLeader', $config, false) ?? true; } /** @@ -120,16 +112,21 @@ public function info() */ public function exists(array $options = []) { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += [ + 'name' => $this->name() + ]; + try { - $this->connection->getSession($options + [ - 'name' => $this->name(), - 'database' => $this->databaseName - ]); + $request = $this->serializer->decodeMessage(new GetSessionRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->databaseName); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - return true; + $this->spannerClient->getSession($request, $callOptions); } catch (NotFoundException $e) { return false; } + return true; } /** @@ -140,10 +137,15 @@ public function exists(array $options = []) */ public function delete(array $options = []) { - $this->connection->deleteSession($options + [ - 'name' => $this->name(), - 'database' => $this->databaseName - ]); + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data = [ + 'name' => $this->name() + ]; + + $request = $this->serializer->decodeMessage(new DeleteSessionRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->databaseName); + + $this->spannerClient->deleteSession($request, $callOptions); } /** @@ -188,7 +190,7 @@ public function expiration() public function __debugInfo() { return [ - 'connection' => get_class($this->connection), + 'spannerClient' => get_class($this->spannerClient), 'projectId' => $this->projectId, 'instance' => $this->instance, 'database' => $this->database, diff --git a/Spanner/src/Snapshot.php b/Spanner/src/Snapshot.php index a6d6081104f7..ab67f8c97b5c 100644 --- a/Spanner/src/Snapshot.php +++ b/Spanner/src/Snapshot.php @@ -18,7 +18,6 @@ namespace Google\Cloud\Spanner; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\Session\SessionPoolInterface; /** * Read-only snapshot Transaction. @@ -30,7 +29,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $database = $spanner->connect('my-instance', 'my-database'); * $transaction = $database->snapshot(); @@ -60,7 +59,7 @@ public function __construct(Operation $operation, Session $session, array $optio { if (isset($options['tag'])) { throw new \InvalidArgumentException( - "Cannot set a transaction tag on a read-only transaction." + 'Cannot set a transaction tag on a read-only transaction.' ); } $this->initialize($operation, $session, $options); diff --git a/Spanner/src/SnapshotTrait.php b/Spanner/src/SnapshotTrait.php index b01850b6f277..53bbadcd1455 100644 --- a/Spanner/src/SnapshotTrait.php +++ b/Spanner/src/SnapshotTrait.php @@ -17,15 +17,16 @@ namespace Google\Cloud\Spanner; +use Google\ApiCore\ArrayTrait; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; -use Google\Cloud\Spanner\Timestamp; /** * Common methods for Read-Only transactions (i.e. Snapshots) */ trait SnapshotTrait { + use ArrayTrait; use TransactionalReadTrait; /** @@ -65,9 +66,9 @@ private function initialize( throw new \InvalidArgumentException('$options.readTimestamp must be an instance of Timestamp.'); } - $this->transactionId = $options['id'] ?: null; - $this->readTimestamp = $options['readTimestamp']; - $this->type = $options['id'] + $this->transactionId = $this->pluck('id', $options) ?: null; + $this->readTimestamp = $this->pluck('readTimestamp', $options) ?: null; + $this->type = $this->transactionId ? self::TYPE_PRE_ALLOCATED : self::TYPE_SINGLE_USE; diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index 9c0d4dcab944..e5fa9d6dddae 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -17,30 +17,35 @@ namespace Google\Cloud\Spanner; +use Google\ApiCore\ClientOptionsTrait; +use Google\ApiCore\CredentialsWrapper; +use Google\ApiCore\Middleware\MiddlewareInterface; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\ValidationException; use Google\Auth\FetchAuthTokenInterface; -use Google\Cloud\Core\ArrayTrait; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\ClientTrait; use Google\Cloud\Core\Exception\GoogleException; +use Google\Cloud\Core\EmulatorTrait; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Iterator\PageIterator; -use Google\Cloud\Core\LongRunning\LongRunningOperation; -use Google\Cloud\Core\LongRunning\LROTrait; +use Google\Cloud\Core\Middleware\ExceptionMiddleware; +use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Core\ValidateTrait; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigOperationsRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; use Google\Cloud\Spanner\Batch\BatchClient; -use Google\Cloud\Spanner\Connection\Grpc; -use Google\Cloud\Spanner\Connection\LongRunningConnection; use Google\Cloud\Spanner\Session\SessionPoolInterface; -use Google\Cloud\Spanner\Numeric; -use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; -use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; -use Google\Cloud\Spanner\V1\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\LongRunning\ListOperationsRequest; +use Google\Protobuf\Duration; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\StreamInterface; -use Google\ApiCore\ValidationException; /** * Cloud Spanner is a highly scalable, transactional, managed, NewSQL @@ -59,7 +64,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * ``` * * ``` @@ -70,14 +75,15 @@ * // `9010` is used as an example only. * putenv('SPANNER_EMULATOR_HOST=localhost:9010'); * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * ``` * * ``` * use Google\Cloud\Spanner\SpannerClient; * use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; * - * $directedOptions = [ + * $config = [ + * 'projectId' => 'my-project', * 'directedReadOptions' => [ * 'includeReplicas' => [ * 'replicaSelections' => [ @@ -90,46 +96,46 @@ * ] * ] * ]; - * $spanner = new SpannerClient($directedOptions); + * $spanner = new SpannerClient($config); * ``` * * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $config = ['routeToLeader' => false]; + * $config = ['projectId' => 'my-project', 'routeToLeader' => false]; * $spanner = new SpannerClient($config); * ``` - * - * @method resumeOperation() { - * Resume a Long Running Operation - * - * Example: - * ``` - * $operation = $spanner->resumeOperation($operationName); - * ``` - * - * @param string $operationName The Long Running Operation name. - * @param array $info [optional] The operation data. - * @return LongRunningOperation - * } */ class SpannerClient { - use ArrayTrait; + use ClientOptionsTrait; use ClientTrait; - use LROTrait; - use ValidateTrait; + use EmulatorTrait; + use RequestTrait; const VERSION = '1.89.0'; const FULL_CONTROL_SCOPE = 'https://www.googleapis.com/auth/spanner.data'; const ADMIN_SCOPE = 'https://www.googleapis.com/auth/spanner.admin'; + private GapicSpannerClient $spannerClient; + private InstanceAdminClient $instanceAdminClient; + private DatabaseAdminClient $databaseAdminClient; + + /** + * @var Serializer + */ + private Serializer $serializer; + + /** + * @var string + */ + private $projectId; + /** - * @var Connection\ConnectionInterface - * @internal + * @var string */ - protected $connection; + private $projectName; /** * @var bool @@ -141,6 +147,16 @@ class SpannerClient */ private $directedReadOptions; + /** + * @var bool + */ + private $routeToLeader; + + /** + * @var array + */ + private $defaultQueryOptions; + /** * Create a Spanner client. Please note that this client requires * [the gRPC extension](https://cloud.google.com/php/grpc). @@ -148,31 +164,22 @@ class SpannerClient * @param array $config [optional] { * Configuration Options. * + * @type string $projectId The Google Cloud project ID. * @type string $apiEndpoint A hostname with optional port to use in * place of the service's default endpoint. * @type string $projectId The project ID from the Google Developer's * Console. - * @type CacheItemPoolInterface $authCache A cache for storing access + * @type CacheItemPoolInterface $credentialsConfig.authCache A cache for storing access * tokens. **Defaults to** a simple in memory implementation. - * @type array $authCacheOptions Cache configuration options. - * @type callable $authHttpHandler A handler used to deliver Psr7 + * @type array $credentialsConfig.authCacheOptions Cache configuration options. + * @type callable $credentialsConfig.authHttpHandler A handler used to deliver Psr7 * requests specifically for authentication. - * @type FetchAuthTokenInterface $credentialsFetcher A credentials - * fetcher instance. - * @type callable $httpHandler A handler used to deliver Psr7 requests. + * @type callable $transportConfig.rest.httpHandler A handler used to deliver Psr7 requests. * Only valid for requests sent over REST. - * @type array $keyFile The contents of the service account credentials - * .json file retrieved from the Google Developer's Console. - * Ex: `json_decode(file_get_contents($path), true)`. - * @type string $keyFilePath The full path to your service account - * credentials .json file retrieved from the Google Developers - * Console. - * @type float $requestTimeout Seconds to wait before timing out the - * request. **Defaults to** `0` with REST and `60` with gRPC. - * @type int $retries Number of retries for a failed request. Used only - * with default backoff strategy. **Defaults to** `3`. - * @type array $scopes Scopes to be used for the request. - * @type string $quotaProject Specifies a user project to bill for + * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials + * The credentials to be used by the client to authorize API calls. + * @type array $credentialsConfig.scopes Scopes to be used for the request. + * @type string $credentialsConfig.quotaProject Specifies a user project to bill for * access charges associated with the request. * @type bool $returnInt64AsObject If true, 64 bit integers will be * returned as a {@see \Google\Cloud\Core\Int64} object for 32 bit @@ -192,10 +199,6 @@ class SpannerClient * query execution. Executing a SQL statement with an invalid * optimizer version will fail with a syntax error * (`INVALID_ARGUMENT`) status. - * @type bool $useDiscreteBackoffs `false`: use default backoff strategy - * (retry every failed request up to `retries` times). - * `true`: use discrete backoff settings based on called method name. - * **Defaults to** `false`. * @type array $directedReadOptions Directed read options. * {@see \Google\Cloud\Spanner\V1\DirectedReadOptions} * If using the `replicaSelection::type` setting, utilize the constants available in @@ -210,11 +213,8 @@ public function __construct(array $config = []) $emulatorHost = getenv('SPANNER_EMULATOR_HOST'); $this->requireGrpc(); + $scopes = [self::FULL_CONTROL_SCOPE, self::ADMIN_SCOPE]; $config += [ - 'scopes' => [ - self::FULL_CONTROL_SCOPE, - self::ADMIN_SCOPE - ], 'returnInt64AsObject' => false, 'projectIdRequired' => true, 'hasEmulator' => (bool) $emulatorHost, @@ -222,64 +222,111 @@ public function __construct(array $config = []) 'queryOptions' => [] ]; - if (!empty($config['useDiscreteBackoffs'])) { - $config = array_merge_recursive($config, [ - 'retries' => 0, - 'grpcOptions' => [ - 'retrySettings' => [], - ], - ]); - } - - $this->connection = new Grpc($this->configureAuthentication($config)); $this->returnInt64AsObject = $config['returnInt64AsObject']; + $this->directedReadOptions = $config['directedReadOptions'] ?? []; + $this->routeToLeader = $config['routeToLeader'] ?? true; + $this->defaultQueryOptions = $config['queryOptions']; + + // Configure GAPIC client options + $config = $this->buildClientOptions($config); + if (isset($config['credentialsConfig']['scopes'])) { + $config['credentialsConfig']['scopes'] = array_merge( + $config['credentialsConfig']['scopes'], + $scopes + ); + } else { + $config['credentialsConfig']['scopes'] = $scopes; + } - $this->setLroProperties(new LongRunningConnection($this->connection), [ - [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceMetadata', - 'callable' => function ($instance) { - $name = InstanceAdminClient::parseName($instance['name'])['instance']; - return $this->instance($name, $instance); + if ($emulatorHost) { + $emulatorConfig = $this->emulatorGapicConfig($emulatorHost); + $config = array_merge( + $config, + $emulatorConfig + ); + } else { + $config['credentials'] = $this->createCredentialsWrapper( + $config['credentials'], + $config['credentialsConfig'], + $config['universeDomain'] + ); + } + $this->projectId = $this->detectProjectId($config); + $this->serializer = new Serializer([], [ + 'google.spanner.v1.KeySet' => function ($v) { + // exit("TEST"); + $keys = $this->pluck('keys', $keySet, false); + if ($keys) { + $keySet['keys'] = array_map( + fn ($key) => $this->formatListForApi((array) $key), + $keys + ); } - ], [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateDatabaseMetadata', - 'callable' => function ($database) { - $databaseNameComponents = DatabaseAdminClient::parseName($database['name']); - $instanceName = $databaseNameComponents['instance']; - $databaseName = $databaseNameComponents['database']; - - $instance = $this->instance($instanceName); - return $instance->database($databaseName); + + if (isset($keySet['ranges'])) { + $keySet['ranges'] = array_map(function ($rangeItem) { + return array_map([$this, 'formatListForApi'], $rangeItem); + }, $keySet['ranges']); + + if (empty($keySet['ranges'])) { + unset($keySet['ranges']); + } } - ], [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata', - 'callable' => function ($database) { - $databaseNameComponents = DatabaseAdminClient::parseName($database['name']); - $instanceName = $databaseNameComponents['instance']; - $databaseName = $databaseNameComponents['database']; - - $instance = $this->instance($instanceName); - return $instance->database($databaseName); + + return $this->decodeMessage(new KeySet(), $keySet); + }, + ], [], [], [ + // A custom encoder that short-circuits the encodeMessage in Serializer class, + // but only if the argument is of the type PartialResultSet. + PartialResultSet::class => function ($msg) { + $data = json_decode($msg->serializeToJsonString(), true); + + // We only override metadata fields, if it actually exists in the response. + // This is specially important for large data sets which is received in chunks. + // Metadata is only received in the first 'chunk' and we don't want to set empty metadata fields + // when metadata was not returned from the server. + if (isset($data['metadata'])) { + // The transaction id is serialized as a base64 encoded string in $data. So, we + // add a step to get the transaction id using a getter instead of the serialized value. + // The null-safe operator is used to handle edge cases where the relevant fields are not present. + $data['metadata']['transaction']['id'] = (string) $msg?->getMetadata()?->getTransaction()?->getId(); + + // Helps convert metadata enum values from string types to their respective code/annotation + // pairs. Ex: INT64 is converted to {code: 2, typeAnnotation: 0}. + $fields = $msg->getMetadata()?->getRowType()?->getFields(); + $data['metadata']['rowType']['fields'] = $this->getFieldDataFromRepeatedFields($fields); } - ],[ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceMetadata', - 'callable' => function ($instance) { - $name = InstanceAdminClient::parseName($instance['name'])['instance']; - return $this->instance($name, $instance); + + // These fields in stats should be an int + if (isset($data['stats']['rowCountLowerBound'])) { + $data['stats']['rowCountLowerBound'] = (int) $data['stats']['rowCountLowerBound']; } - ], [ - 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata', - 'callable' => function ($backup) { - $backupNameComponents = DatabaseAdminClient::parseName($backup['name']); - $instanceName = $backupNameComponents['instance']; - - $instance = $this->instance($instanceName); - return $instance->backup($backup['name'], $backup); + if (isset($data['stats']['rowCountExact'])) { + $data['stats']['rowCountExact'] = (int) $data['stats']['rowCountExact']; } - ] + + return $data; + } ]); - $this->directedReadOptions = $config['directedReadOptions'] ?? []; + // Adds some defaults + // gccl needs to be present for handwritten clients + $clientConfig = $config += [ + 'libName' => 'gccl', + 'serializer' => $this->serializer, + ]; + $middleware = function (MiddlewareInterface $handler) { + return new ExceptionMiddleware($handler); + }; + $this->spannerClient = $config['gapicSpannerClient'] ?? new GapicSpannerClient($clientConfig); + $this->spannerClient->addMiddleware($middleware); + $this->instanceAdminClient = $config['gapicSpannerInstanceAdminClient'] + ?? new InstanceAdminClient($clientConfig); + $this->instanceAdminClient->addMiddleware($middleware); + $this->databaseAdminClient = $config['gapicSpannerDatabaseAdminClient'] + ?? new DatabaseAdminClient($clientConfig); + $this->databaseAdminClient->addMiddleware($middleware); + $this->projectName = InstanceAdminClient::projectName($this->projectId); } /** @@ -311,8 +358,13 @@ public function __construct(array $config = []) public function batch($instanceId, $databaseId, array $options = []) { $operation = new Operation( - $this->connection, - $this->returnInt64AsObject + $this->spannerClient, + $this->serializer, + $this->returnInt64AsObject, + [ + 'routeToLeader' => $this->routeToLeader, + 'defaultQueryOptions' => $this->defaultQueryOptions + ] ); return new BatchClient( @@ -379,7 +431,7 @@ public function batch($instanceId, $databaseId, array $options = []) * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return LongRunningOperation + * @return OperationResponse * @throws ValidationException */ public function createInstanceConfiguration(InstanceConfiguration $baseConfig, $name, array $replicas, array $options = []) @@ -414,15 +466,27 @@ public function createInstanceConfiguration(InstanceConfiguration $baseConfig, $ */ public function instanceConfigurations(array $options = []) { - $resultLimit = $this->pluck('resultLimit', $options, false) ?: 0; + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['parent'] = $this->projectName; + $resultLimit = $this->pluck('resultLimit', $options, false) ?: 0; return new ItemIterator( new PageIterator( function (array $config) { return $this->instanceConfiguration($config['name'], $config); }, - [$this->connection, 'listInstanceConfigs'], - ['projectName' => InstanceAdminClient::projectName($this->projectId)] + $options, + function ($callOptions) use ($data) { + if (isset($callOptions['pageToken'])) { + $data['pageToken'] = $callOptions['pageToken']; + } + + $request = $this->serializer->decodeMessage(new ListInstanceConfigsRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); + + $response = $this->instanceAdminClient->listInstanceConfigs($request, $callOptions); + return $this->handleResponse($response); + }, + $callOptions, [ 'itemsKey' => 'instanceConfigs', 'resultLimit' => $resultLimit @@ -456,11 +520,11 @@ function (array $config) { public function instanceConfiguration($name, array $options = []) { return new InstanceConfiguration( - $this->connection, + $this->instanceAdminClient, + $this->serializer, $this->projectId, $name, - $options, - $this->lroConnection + $options ); } @@ -488,23 +552,28 @@ public function instanceConfiguration($name, array $options = []) * been generated by a previous call to the API. * } * - * @return ItemIterator + * @return ItemIterator */ public function instanceConfigOperations(array $options = []) { - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $operation) { - return $this->resumeOperation($operation['name'], $operation); - }, - [$this->connection, 'listInstanceConfigOperations'], - ['projectName' => InstanceAdminClient::projectName($this->projectId)] + $options, - [ - 'itemsKey' => 'operations', - 'resultLimit' => $resultLimit - ] - ) + [$data, $callOptions] = $this->splitOptionalArgs($options); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); + $request = $this->serializer->decodeMessage(new ListInstanceConfigOperationsRequest(), $data); + $request->setParent($this->projectName); + + return $this->buildLongRunningIterator( + [$this->instanceAdminClient, 'listInstanceConfigOperations'], + $request, + $callOptions, + function (Operation $operation) { + return $this->resumeOperation( + $operation->getName(), + ['lastProtoResponse' => $operation] + )->withResultFunction(function (InstanceConfig $result) { + $config = $this->serializer->encodeMessage($result); + return $this->instanceConfiguration($config['name'], $config); + }); + }, ); } @@ -529,7 +598,7 @@ function (array $operation) { * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://cloudplatform.googleblog.com/2015/10/using-labels-to-organize-Google-Cloud-Platform-resources.html). * } - * @return LongRunningOperation + * @return OperationResponse * @codingStandardsIgnoreEnd */ public function createInstance(InstanceConfiguration $config, $name, array $options = []) @@ -552,14 +621,19 @@ public function createInstance(InstanceConfiguration $config, $name, array $opti public function instance($name, array $instance = []) { return new Instance( - $this->connection, - $this->lroConnection, - $this->lroCallables, + $this->spannerClient, + $this->instanceAdminClient, + $this->databaseAdminClient, + $this->serializer, $this->projectId, $name, $this->returnInt64AsObject, $instance, - ['directedReadOptions' => $this->directedReadOptions] + [ + 'directedReadOptions' => $this->directedReadOptions, + 'routeToLeader' => $this->routeToLeader, + 'defaultQueryOptions' => $this->defaultQueryOptions + ] ); } @@ -591,19 +665,28 @@ public function instance($name, array $instance = []) */ public function instances(array $options = []) { - $options += [ - 'filter' => null - ]; + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data += ['filter' => '', 'parent' => $this->projectName]; - $resultLimit = $this->pluck('resultLimit', $options, false); + $resultLimit = $this->pluck('resultLimit', $data, false); return new ItemIterator( new PageIterator( function (array $instance) { $name = InstanceAdminClient::parseName($instance['name'])['instance']; return $this->instance($name, $instance); }, - [$this->connection, 'listInstances'], - ['projectName' => InstanceAdminClient::projectName($this->projectId)] + $options, + function ($callOptions) use ($data) { + if (isset($callOptions['pageToken'])) { + $data['pageToken'] = $callOptions['pageToken']; + } + + $request = $this->serializer->decodeMessage(new ListInstancesRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); + + $response = $this->instanceAdminClient->listInstances($request, $callOptions); + return $this->handleResponse($response); + }, + $callOptions, [ 'itemsKey' => 'instances', 'resultLimit' => $resultLimit @@ -650,6 +733,26 @@ public function connect($instance, $name, array $options = []) return $database; } + /** + * Resume a Long Running Operation + * + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return OperationResponse + */ + public function resumeOperation($operationName, array $options = []) + { + return new OperationResponse( + $operationName, + $this->databaseAdminClient->getOperationsClient(), + $options + ); + } + /** * Create a new KeySet object * @@ -870,7 +973,7 @@ public function int64($value) */ public function duration($seconds, $nanos = 0) { - return new Duration($seconds, $nanos); + return new Duration(['seconds' => $seconds, 'nanos' => $nanos]); } /** @@ -889,6 +992,6 @@ public function duration($seconds, $nanos = 0) */ public function commitTimestamp() { - return new CommitTimestamp; + return new CommitTimestamp(); } } diff --git a/Spanner/src/StructType.php b/Spanner/src/StructType.php index 0f5fcf756f92..8f0de6c1c3b8 100644 --- a/Spanner/src/StructType.php +++ b/Spanner/src/StructType.php @@ -26,7 +26,7 @@ * use Google\Cloud\Spanner\Database; * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $database = $spanner->connect('my-instance', 'my-database'); * * $res = $database->execute('SELECT @userStruct.firstName, @userStruct.lastName', [ diff --git a/Spanner/src/StructValue.php b/Spanner/src/StructValue.php index 7cde1caffa00..77227acf2137 100644 --- a/Spanner/src/StructValue.php +++ b/Spanner/src/StructValue.php @@ -40,7 +40,7 @@ * use Google\Cloud\Spanner\StructType; * use Google\Cloud\Spanner\StructValue; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * $database = $spanner->connect('my-instance', 'my-database'); * * $res = $database->execute('SELECT * FROM UNNEST(ARRAY(SELECT @structParam))', [ diff --git a/Spanner/src/Timestamp.php b/Spanner/src/Timestamp.php index 01b239881bb4..8d53b271bf0b 100644 --- a/Spanner/src/Timestamp.php +++ b/Spanner/src/Timestamp.php @@ -32,7 +32,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $timestamp = $spanner->timestamp(new \DateTime('2003-02-05 11:15:02.421827Z')); * ``` diff --git a/Spanner/src/Transaction.php b/Spanner/src/Transaction.php index 0db1e79cef97..ac62ad8deeaf 100644 --- a/Spanner/src/Transaction.php +++ b/Spanner/src/Transaction.php @@ -21,6 +21,7 @@ use Google\Cloud\Core\Exception\AbortedException; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; +use Google\Protobuf\Duration; /** * Manages interaction with Cloud Spanner inside a Transaction. @@ -47,7 +48,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $database = $spanner->connect('my-instance', 'my-database'); * @@ -78,13 +79,6 @@ class Transaction implements TransactionalReadInterface */ private $mutations = []; - /** - * @var bool - */ - private $isRetry = false; - - private ValueMapper $mapper; - /** * @param Operation $operation The Operation instance. * @param Session $session The session to use for spanner interactions. @@ -103,35 +97,27 @@ class Transaction implements TransactionalReadInterface * @throws \InvalidArgumentException if a tag is specified on a single-use transaction. */ public function __construct( - Operation $operation, - Session $session, - $transactionId = null, - $isRetry = false, - $tag = null, - $options = [], - $mapper = null + private Operation $operation, + private Session $session, + private ?string $transactionId = null, + private bool $isRetry = false, + ?string $tag = null, + array $options = [], + private ?ValueMapper $mapper = null ) { - $this->operation = $operation; - $this->session = $session; - $this->transactionId = $transactionId; - $this->isRetry = $isRetry; - $this->type = ($transactionId || isset($options['begin'])) ? self::TYPE_PRE_ALLOCATED : self::TYPE_SINGLE_USE; if ($this->type == self::TYPE_SINGLE_USE && isset($tag)) { throw new \InvalidArgumentException( - "Cannot set a transaction tag on a single-use transaction." + 'Cannot set a transaction tag on a single-use transaction.' ); } - $this->tag = $tag; $this->context = SessionPoolInterface::CONTEXT_READWRITE; $this->options = $options; - if (!is_null($mapper)) { - $this->mapper = $mapper; - } + $this->tag = $tag; } /** @@ -531,6 +517,8 @@ private function buildUpdateOptions(array $options): array $selector = $this->transactionSelector($options); $options['transaction'] = $selector[0]; - return $this->addLarHeader($options); + $options['headers']['spanner-route-to-leader'] = ['true']; + + return $options; } } diff --git a/Spanner/src/TransactionConfigurationTrait.php b/Spanner/src/TransactionConfigurationTrait.php index 642279a39b2d..aa66f0e51eff 100644 --- a/Spanner/src/TransactionConfigurationTrait.php +++ b/Spanner/src/TransactionConfigurationTrait.php @@ -17,7 +17,7 @@ namespace Google\Cloud\Spanner; -use Google\Cloud\Core\ArrayTrait; +use Google\ApiCore\ArrayTrait; use Google\Cloud\Spanner\Session\SessionPoolInterface; /** @@ -81,7 +81,7 @@ private function configureDirectedReadOptions(array $requestOptions, array $clie if (isset($requestOptions['transaction']['singleUse']) || ( isset($requestOptions['transactionContext']) && $requestOptions['transactionContext'] == SessionPoolInterface::CONTEXT_READ - ) || isset($requestOptions['transactionOptions']['readOnly']) + ) || isset($requestOptions['transactionOptions']['readOnly']) ) { if (isset($clientOptions['includeReplicas'])) { return ['includeReplicas' => $clientOptions['includeReplicas']]; @@ -203,11 +203,6 @@ private function configureSnapshotOptions(array &$options, array $previous = []) 'readTimestamp' ]; - $durationFields = [ - 'exactStaleness', - 'maxStaleness' - ]; - foreach ($timestampFields as $tsf) { if (isset($transactionOptions['readOnly'][$tsf]) && !isset($previousOptions[$tsf])) { $field = $transactionOptions['readOnly'][$tsf]; @@ -223,21 +218,6 @@ private function configureSnapshotOptions(array &$options, array $previous = []) } } - foreach ($durationFields as $df) { - if (isset($transactionOptions['readOnly'][$df]) && !isset($previousOptions[$df])) { - $field = $transactionOptions['readOnly'][$df]; - if (!($field instanceof Duration)) { - throw new \BadMethodCallException(sprintf( - 'Read Only Transaction Configuration Field %s must be an instance of `%s`.', - $df, - Duration::class - )); - } - - $transactionOptions['readOnly'][$df] = $field->get(); - } - } - return $transactionOptions; } } diff --git a/Spanner/src/TransactionalReadTrait.php b/Spanner/src/TransactionalReadTrait.php index 7186eb5e670c..b80415e3ce82 100644 --- a/Spanner/src/TransactionalReadTrait.php +++ b/Spanner/src/TransactionalReadTrait.php @@ -26,57 +26,57 @@ trait TransactionalReadTrait { use TransactionConfigurationTrait; - use RequestHeaderTrait; + // use RequestTrait; /** * @var Operation */ - private $operation; + private Operation $operation; /** * @var Session */ - private $session; + private Session $session; /** * @var string */ - private $transactionId; + private ?string $transactionId; /** * @var string */ - private $context; + private string $context; /** * @var int */ - private $type; + private int $type; /** * @var int */ - private $state = 0; // TransactionalReadInterface::STATE_ACTIVE + private int $state = TransactionalReadInterface::STATE_ACTIVE; /** * @var array */ - private $options = []; + private array $options = []; /** * @var int */ - private $seqno = 1; + private int $seqno = 1; /** * @var string */ - private $tag = null; + private ?string $tag = null; /** * @var array */ - private $directedReadOptions = []; + private array $directedReadOptions = []; /** * Run a query. @@ -303,9 +303,16 @@ public function execute($sql, array $options = []) $this->directedReadOptions ?? [] ); - $options = $this->addLarHeader($options, true, $this->context); + if ($this->context === SessionPoolInterface::CONTEXT_READWRITE) { + // add LAR header + $options['headers']['x-goog-spanner-route-to-leader'] = ['true']; + } + + // Unsetting the internal flag + unset($options['singleUse']); $result = $this->operation->execute($this->session, $sql, $options); + if (empty($this->id()) && $result->transaction()) { $this->setId($result->transaction()->id()); } @@ -365,7 +372,6 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] $options['transactionId'] = $this->transactionId; } $options['transactionType'] = $this->context; - $options += $this->options; $selector = $this->transactionSelector($options, $this->options); $options['transaction'] = $selector[0]; @@ -383,7 +389,10 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] $this->directedReadOptions ?? [] ); - $options = $this->addLarHeader($options, true, $this->context); + if ($this->context === SessionPoolInterface::CONTEXT_READWRITE) { + // add LAR header + $options['headers']['x-goog-spanner-route-to-leader'] = ['true']; + } $result = $this->operation->read($this->session, $table, $keySet, $columns, $options); if (empty($this->id()) && $result->transaction()) { diff --git a/Spanner/src/ValueMapper.php b/Spanner/src/ValueMapper.php index 57771a434d6e..542457872875 100644 --- a/Spanner/src/ValueMapper.php +++ b/Spanner/src/ValueMapper.php @@ -20,8 +20,8 @@ use Google\Cloud\Core\ArrayTrait; use Google\Cloud\Core\Int64; use Google\Cloud\Core\TimeTrait; -use Google\Cloud\Spanner\V1\TypeCode; use Google\Cloud\Spanner\V1\TypeAnnotationCode; +use Google\Cloud\Spanner\V1\TypeCode; /** * Manage value mappings between Google Cloud PHP and Cloud Spanner @@ -150,11 +150,11 @@ public function formatParamsForExecuteSql(array $parameters, array $types = []) $definition = null; if ($type) { - list ($type, $definition) = $this->resolveTypeDefinition($type, $key); + list($type, $definition) = $this->resolveTypeDefinition($type, $key); } $paramDefinition = $this->paramType($value, $type, $definition); - list ($parameters[$key], $paramTypes[$key]) = $paramDefinition; + list($parameters[$key], $paramTypes[$key]) = $paramDefinition; } return [ @@ -446,14 +446,14 @@ private function paramType( break; case 'object': - list ($type, $value) = $this->objectParam($value); + list($type, $value) = $this->objectParam($value); break; case 'array': if ($givenType === Database::TYPE_STRUCT) { if (!($definition instanceof StructType)) { throw new \InvalidArgumentException( - 'Struct parameter types must be declared explicitly, and must '. + 'Struct parameter types must be declared explicitly, and must ' . 'be an instance of Google\Cloud\Spanner\StructType.' ); } @@ -462,7 +462,7 @@ private function paramType( $value = (array) $value; } - list ($value, $type) = $this->structParam($value, $definition); + list($value, $type) = $this->structParam($value, $definition); } else { if (!($definition instanceof ArrayType)) { throw new \InvalidArgumentException( @@ -470,7 +470,7 @@ private function paramType( ); } - list ($value, $type) = $this->arrayParam($value, $definition, $allowMixedArrayType); + list($value, $type) = $this->arrayParam($value, $definition, $allowMixedArrayType); } break; @@ -691,7 +691,6 @@ private function arrayParam($value, ArrayType $arrayObj, $allowMixedArrayType = throw new \InvalidArgumentException('Array values may not be of mixed type'); } - // get typeCode either from the array type or the first element's inferred type $typeCode = self::isCustomType($arrayObj->type()) ? self::getTypeCodeFromString($arrayObj->type()) diff --git a/Spanner/test.php b/Spanner/test.php new file mode 100644 index 000000000000..67163d1f0884 --- /dev/null +++ b/Spanner/test.php @@ -0,0 +1,36 @@ +getOperationsClient(); +// $request = new ListOperationsRequest(); +// // $request->setName('projects/php-docs-samples-kokoro/operations'); +// $operationsClient->listOperations($request); +// // exit; + +// var_dump(InstanceAdminClient::instanceConfigName('my-project', '')); +// exit; +$spanner = new SpannerClient(); +$config = $spanner->instanceConfiguration('regional-us-central1'); +$config->create(); + +foreach ($spanner->instanceConfigOperations() as $instanceConfig) { + var_dump($instanceConfig->getName()); +} + +// // $spanner->longRunningOperations(); +// foreach ($spanner->instances() as $instance) { +// foreach ($instance->databases() as $database) { +// foreach ($database->longRunningOperations() as $operation) { +// var_dump($operation->getName()); +// } +// // var_dump($operation->getName()); +// } +// } + diff --git a/Spanner/tests/OperationRefreshTrait.php b/Spanner/tests/OperationRefreshTrait.php deleted file mode 100644 index 8707f30fb379..000000000000 --- a/Spanner/tests/OperationRefreshTrait.php +++ /dev/null @@ -1,41 +0,0 @@ -___setProperty('operation', new Operation($connection, $returnInt64AsObject)); - return $stub; - } -} diff --git a/Spanner/tests/Perf/ycsb.php b/Spanner/tests/Perf/ycsb.php index 05749d5bddcd..8d561a067bb7 100644 --- a/Spanner/tests/Perf/ycsb.php +++ b/Spanner/tests/Perf/ycsb.php @@ -56,7 +56,7 @@ $parameters = Config::getParameters(); $report = Report::getReporter(); -$database = (new SpannerClient)->connect($parameters['instance'], $parameters['database']); +$database = (new SpannerClient())->connect($parameters['instance'], $parameters['database']); $totalWeight = 0.0; $weights = []; diff --git a/Spanner/tests/ResultGeneratorTrait.php b/Spanner/tests/ResultGeneratorTrait.php index 6d7ccfc74be8..a6cd118facc0 100644 --- a/Spanner/tests/ResultGeneratorTrait.php +++ b/Spanner/tests/ResultGeneratorTrait.php @@ -17,81 +17,68 @@ namespace Google\Cloud\Spanner\Tests; +use Google\ApiCore\ServerStream; use Google\Cloud\Spanner\Database; +use Google\Cloud\Spanner\Tests\Unit\Fixtures; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\ResultSetMetadata; +use Google\Cloud\Spanner\V1\ResultSetStats; +use Google\Cloud\Spanner\V1\StructType; +use Google\Cloud\Spanner\V1\StructType\Field; +use Google\Cloud\Spanner\V1\Transaction; +use Google\Cloud\Spanner\V1\Type; +use Google\Protobuf\Value; /** * Provide a Spanner Read/Query result */ trait ResultGeneratorTrait { - /** - * Yield a ResultSet response. - * - * @param bool $withStats If true, statistics will be included. - * **Defaults to** `false`. - * @param string|null $transaction If set, the value will be included as the - * transaction ID. **Defaults to** `null`. - * @return \Generator - */ - private function resultGenerator($withStats = false, $transaction = null) - { - return $this->yieldRows([ - [ - 'name' => 'ID', - 'type' => Database::TYPE_INT64, - 'value' => '10' - ] - ], $withStats, $transaction); - } - /** * Yield rows with user-specified data. * * @param array[] $rows A list of arrays containing `name`, `type` and `value` keys. - * @param bool $withStats If true, statistics will be included. + * @param ResultSetStats|null $stats If true, statistics will be included. * **Defaults to** `false`. - * @param string|null $transaction If set, the value will be included as the + * @param string|null $transactionId If set, the value will be included as the * transaction ID. **Defaults to** `null`. * @return \Generator */ - private function yieldRows(array $rows, $withStats = false, $transaction = null) + private function yieldRows(array $rows, $stats = null, $transactionId = null) { $fields = []; $values = []; foreach ($rows as $row) { - $fields[] = [ + $fields[] = new Field([ 'name' => $row['name'], - 'type' => [ - 'code' => $row['type'] - ] - ]; + 'type' => new Type(['code' => $row['type']]) + ]); - $values[] = $row['value']; + $values[] = new Value(['string_value' => $row['value']]); } $result = [ - 'metadata' => [ - 'rowType' => [ + 'metadata' => new ResultSetMetadata([ + 'row_type' => new StructType([ 'fields' => $fields - ] - ], + ]) + ]), 'values' => $values ]; - if ($withStats) { - $result['stats'] = [ - 'rowCountExact' => 1, - 'rowCountLowerBound' => 1 - ]; + if ($stats) { + $result['stats'] = $stats; } - if ($transaction) { - $result['metadata']['transaction'] = [ - 'id' => $transaction - ]; + if ($transactionId) { + $result['metadata']->setTransaction(new Transaction(['id' => $transactionId])); + } + + if (isset($result['stats'])) { + $result['stats'] = $stats; } - yield $result; + yield new PartialResultSet($result); } /** @@ -104,4 +91,71 @@ private function resultGeneratorData(array $data) { yield $data; } + + private function resultGeneratorStream( + array $chunks = null, + ResultSetStats $stats = null, + string $transactionId = null + ) { + $this->stream = $this->prophesize(ServerStream::class); + if ($chunks) { + foreach ($chunks as $i => $chunk) { + $result = new PartialResultSet(); + $result->mergeFromJsonString($chunk); + $chunks[$i] = $result; + } + + $this->stream->readAll() + ->willReturn($this->resultGeneratorChunks($chunks)); + } else { + $rows = [ + [ + 'name' => 'ID', + 'type' => Database::TYPE_INT64, + 'value' => '10' + ] + ]; + $this->stream->readAll() + ->willReturn($this->yieldRows($rows, $stats, $transactionId)); + } + + return $this->stream->reveal(); + } + + private function resultGeneratorChunks($chunks) + { + foreach ($chunks as $chunk) { + yield $chunk; + } + } + + private function resultGeneratorJson($chunks) + { + foreach ($chunks as $chunk) { + yield json_decode($chunk, true); + } + } + + private function getStreamingDataFixture() + { + return json_decode( + file_get_contents(Fixtures::STREAMING_READ_ACCEPTANCE_FIXTURE()), + true + ); + } + + public function streamingDataProviderFirstChunk() + { + foreach ($this->getStreamingDataFixture()['tests'] as $test) { + yield [$test['chunks'], $test['result']['value']]; + break; + } + } + + public function streamingDataProvider() + { + foreach ($this->getStreamingDataFixture()['tests'] as $test) { + yield [$test['chunks'], $test['result']['value']]; + } + } } diff --git a/Spanner/tests/Snippet/ArrayTypeTest.php b/Spanner/tests/Snippet/ArrayTypeTest.php index db0b98de2eac..3f36806155b1 100644 --- a/Spanner/tests/Snippet/ArrayTypeTest.php +++ b/Spanner/tests/Snippet/ArrayTypeTest.php @@ -17,19 +17,21 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\ArrayType; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructType; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\PartialResultSet; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -40,17 +42,17 @@ class ArrayTypeTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; + use ApiHelperTrait; + use ResultGeneratorTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; const INSTANCE = 'my-instance'; - private $connection; private $database; private $type; + private $spannerClient; public function setUp(): void { @@ -76,54 +78,74 @@ public function setUp(): void $sessionPool->setDatabase(Argument::any()) ->willReturn(null); - $this->connection = $this->getConnStub(); - $this->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->serializer = new Serializer(); + + $this->database = new Database( + $this->spannerClient->reveal(), + $this->prophesize(DatabaseAdminClient::class)->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation']); + ); } public function testConstructor() { - $field = [ - 'code' => Database::TYPE_ARRAY, - 'arrayElementType' => [ - 'code' => Database::TYPE_STRING - ] - ]; - - $values = [ - 'foo', 'bar', null - ]; - - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', 'SELECT @arrayParam as arrayValue'), - Argument::withEntry('params', [ - 'arrayParam' => $values - ]), - Argument::withEntry('paramTypes', [ - 'arrayParam' => $field - ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals('SELECT @arrayParam as arrayValue', $request->getSql()); + $this->assertEquals( + ['arrayParam' => ['foo', 'bar', null]], + $message['params'] + ); + $this->assertEquals( + Database::TYPE_STRING, + $message['paramTypes']['arrayParam']['arrayElementType']['code'], + ); + $this->assertEquals( + Database::TYPE_ARRAY, + $message['paramTypes']['arrayParam']['code'], + ); + return true; + }), + Argument::type('array') + )->shouldBeCalled()->willReturn( + $this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'arrayValue', + 'type' => [ + 'code' => Database::TYPE_ARRAY, + 'arrayElementType' => [ + 'code' => Database::TYPE_STRING + ] + ] + ] + ] + ] + ], + 'values' => [ [ - 'name' => 'arrayValue', - 'type' => $field + 'listValue' => [ + 'values' => [ + ['stringValue' => 'foo'], + ['stringValue' => 'bar'], + ['nullValue' => 0] + ] + ] ] ] ] - ], - 'values' => [$values] - ])); - - $this->refreshOperation($this->database, $this->connection->reveal()); + )->serializeToJsonString()]) + ); $snippet = $this->snippetFromClass(ArrayType::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); @@ -140,9 +162,4 @@ public function testArrayTypeStruct() $this->assertEquals(Database::TYPE_STRUCT, $res->type()); $this->assertInstanceOf(StructType::class, $res->structType()); } - - private function resultGenerator(array $data) - { - yield $data; - } } diff --git a/Spanner/tests/Snippet/BackupTest.php b/Spanner/tests/Snippet/BackupTest.php index ea4b68179451..3dff343dc807 100644 --- a/Spanner/tests/Snippet/BackupTest.php +++ b/Spanner/tests/Snippet/BackupTest.php @@ -17,21 +17,34 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\DeleteBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Backup; +use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\SpannerClient; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\ListOperationsRequest; +use Google\LongRunning\ListOperationsResponse; +use Google\LongRunning\Operation; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; - /** +/** * @group spanner * @group spanner-backup */ @@ -45,9 +58,9 @@ class BackupTest extends SnippetTestCase const DATABASE = 'my-database'; const BACKUP = 'my-backup'; - private $connection; + private $serializer; private $backup; - private $client; + private $spanner; private $instance; private $expireTime; @@ -55,25 +68,24 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->prophesize(ConnectionInterface::class); - $this->client = TestHelpers::stub(SpannerClient::class); - $this->expireTime = new \DateTime("+ 7 hours"); - $this->instance = TestHelpers::stub(Instance::class, [ - $this->connection->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], - self::PROJECT, - self::INSTANCE - ], ['connection', 'lroConnection']); - - $this->backup = TestHelpers::stub(Backup::class, [ - $this->connection->reveal(), - $this->instance, - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + + $this->serializer = new Serializer(); + $this->spanner = new SpannerClient(['projectId' => 'my-project']); + $this->expireTime = new \DateTime('+ 7 hours'); + $database = $this->prophesize(Database::class); + $database->name()->willReturn(DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE)); + $instance = $this->prophesize(Instance::class); + $instance->name()->willReturn(InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)); + $instance->database('my-database')->willReturn($database->reveal()); + + $this->backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $instance->reveal(), self::PROJECT, - self::BACKUP, - ], ['instance', 'connection', 'lroConnection']); + self::BACKUP + ); } public function testClass() @@ -93,44 +105,45 @@ public function testCreate() $snippet = $this->snippetFromMethod(Backup::class, 'create'); $snippet->addLocal('backup', $this->backup); - $this->connection->createBackup(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createBackup( + Argument::type(CreateBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + // $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } - public function testCreateCopy() - { - $snippet = $this->snippetFromMethod(Backup::class, 'createCopy'); - $snippet->addLocal('spanner', $this->client); + // public function testCreateCopy() + // { + // $snippet = $this->snippetFromMethod(Backup::class, 'createCopy'); + // $snippet->addLocal('spanner', $this->spanner); - $this->connection->copyBackup(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + // $this->databaseAdminClient->copyBackup( + // Argument::type(CopyBackupRequest::class), + // Argument::type('array') + // ) + // ->shouldBeCalledOnce() + // ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->client->___setProperty('connection', $this->connection->reveal()); - $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); - } + // $res = $snippet->invoke('operation'); + // $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + // } public function testDelete() { $snippet = $this->snippetFromMethod(Backup::class, 'delete'); $snippet->addLocal('backup', $this->backup); - $this->connection->deleteBackup(Argument::any()) - ->shouldBeCalled(); + $this->databaseAdminClient->deleteBackup( + Argument::type(DeleteBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->backup->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -140,11 +153,12 @@ public function testExists() $snippet = $this->snippetFromMethod(Backup::class, 'exists'); $snippet->addLocal('backup', $this->backup); - $this->connection->getBackup(Argument::any()) - ->shouldBeCalled() - ->willReturn(['foo' => 'bar']); - - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new BackupProto()); $res = $snippet->invoke(); $this->assertEquals('Backup exists!', $res->output()); @@ -157,14 +171,16 @@ public function testInfo() $snippet = $this->snippetFromMethod(Backup::class, 'info'); $snippet->addLocal('backup', $this->backup); - $this->connection->getBackup(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn($backup); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new BackupProto($backup)); - $this->backup->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); - $this->assertEquals($backup, $res->returnVal()); + $this->assertEquals($backup['name'], $res->returnVal()['name']); $snippet->invoke(); } @@ -182,19 +198,21 @@ public function testName() public function testReload() { - $bkp = ['name' => 'foo']; + $backup = ['name' => 'foo']; $snippet = $this->snippetFromMethod(Backup::class, 'reload'); $snippet->addLocal('backup', $this->backup); - $this->connection->getBackup(Argument::any()) + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) ->shouldBeCalledTimes(2) - ->willReturn($bkp); + ->willReturn(new BackupProto($backup)); - $this->backup->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); - $this->assertEquals($bkp, $res->returnVal()); + $this->assertEquals($backup['name'], $res->returnVal()['name']); $snippet->invoke(); } @@ -203,11 +221,13 @@ public function testState() $snippet = $this->snippetFromMethod(Backup::class, 'state'); $snippet->addLocal('backup', $this->backup); - $this->connection->getBackup(Argument::any()) - ->shouldBeCalledTimes(1) - ->WillReturn(['state' => Backup::STATE_READY]); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new BackupProto(['state' => Backup::STATE_READY])); - $this->backup->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke(); $this->assertEquals('Backup is ready!', $res->output()); @@ -215,30 +235,40 @@ public function testState() public function testUpdateExpireTime() { - $bkp = ['name' => 'foo', 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z')]; + $backup = [ + 'name' => 'foo', + 'expire_time' => new \Google\Protobuf\Timestamp(['seconds' => $this->expireTime->format('U')]) + ]; $snippet = $this->snippetFromMethod(Backup::class, 'updateExpireTime'); $snippet->addLocal('backup', $this->backup); - $this->connection->updateBackup(Argument::any()) - ->shouldBeCalled() - ->willReturn($bkp); + $this->databaseAdminClient->updateBackup( + Argument::type(UpdateBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new BackupProto($backup)); - $this->backup->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); - $this->assertEquals($bkp, $res->returnVal()); + $this->assertEquals($backup['name'], $res->returnVal()['name']); + $this->assertEquals( + $this->expireTime->format('Y-m-d\TH:i:s.000000\Z'), + $res->returnVal()['expireTime'] + ); } public function testResumeOperation() { $snippet = $this->snippetFromMagicMethod(Backup::class, 'resumeOperation'); + $snippet->addLocal('spanner', $this->spanner); $snippet->addLocal('backup', $this->backup); $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); - $this->assertEquals('foo', $res->returnVal()->name()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()->getName()); } public function testLongRunningOperations() @@ -246,21 +276,30 @@ public function testLongRunningOperations() $snippet = $this->snippetFromMethod(Backup::class, 'longRunningOperations'); $snippet->addLocal('backup', $this->backup); - $lroConnection = $this->prophesize(LongRunningConnectionInterface::class); - $lroConnection->operations(Argument::any()) + $operation = new Operation(); + + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListOperationsResponse(['operations' => [$operation]])); + $page->getNextPageToken() + ->willReturn(null); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + + $operationsClient = $this->prophesize(OperationsClient::class); + $operationsClient->listOperations( + Argument::type(ListOperationsRequest::class), + Argument::type('array') + ) + ->willReturn($pagedListResponse->reveal()); + + $this->databaseAdminClient->getOperationsClient() ->shouldBeCalled() - ->willReturn([ - 'operations' => [ - [ - 'name' => 'foo' - ] - ] - ]); - - $this->backup->___setProperty('lroConnection', $lroConnection->reveal()); + ->willReturn($operationsClient->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); } } diff --git a/Spanner/tests/Snippet/Batch/BatchClientTest.php b/Spanner/tests/Snippet/Batch/BatchClientTest.php index f4a3e9319f2b..fc02fd1dcac1 100644 --- a/Spanner/tests/Snippet/Batch/BatchClientTest.php +++ b/Spanner/tests/Snippet/Batch/BatchClientTest.php @@ -17,6 +17,7 @@ namespace Google\Cloud\Spanner\Tests\Snippet\Batch; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\RequestHandler; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; @@ -27,12 +28,10 @@ use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\QueryPartition; -use Google\Cloud\Spanner\Connection\ConnectionInterface; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Prophecy\Argument; /** @@ -42,25 +41,24 @@ class BatchClientTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; - use StubCreationTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; - private $connection; + private $spannerClient; + private $serializer; private $client; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->connection->reveal(), false), + $this->serializer = new Serializer(); + $this->client = new BatchClient( + new Operation($this->requestHandler->reveal(), $this->serializer, false), self::DATABASE - ], ['operation']); + ); } public function testClass() @@ -138,53 +136,61 @@ public function testPubSubExample() ] ]); - $pubsub->___setProperty('requestHandler', $requestHandler->reveal()); // setup spanner service call stubs - $this->connection->partitionQuery(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->partitionQuery( + null, + [ 'partitions' => [ ['partitionToken' => $partition1->token()], ['partitionToken' => $partition2->token()] ] - ]); - - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('partitionToken', $partition1->token()), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('session', self::SESSION) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'loginCount', - 'type' => [ - 'code' => Database::TYPE_INT64 + ] + ); + + $this->spannerClient->executeStreamingSql( + function ($args) use ($partition1) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals( + $message['partitionToken'], + $partition1->token() + ); + $this->assertEquals( + $message['transaction']['id'], + self::TRANSACTION + ); + $this->assertEquals($message['session'], self::SESSION); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'loginCount', + 'type' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] - ] - ], - 'values' => [0] - ])); - - $this->connection->createSession(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + ], + 'values' => [0] + ]) + ); + $this->spannerClient->createSession( + null, + ['name' => self::SESSION] + ); + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ]); + ] + ); - $this->connection->deleteSession(Argument::any()) - ->shouldBeCalled(); + $this->mockSendRequest(SpannerClient::class, 'deleteSession', null, null); // inject clients $publisher->addLocal('batch', $this->client); @@ -196,7 +202,7 @@ public function testPubSubExample() $subscriber->replace('$pubsub = new PubSubClient();', ''); $publisher->insertAfterLine(0, 'function processResult($res) {iterator_to_array($res);}'); - $this->refreshOperation($this->client, $this->connection->reveal()); + $this->refreshOperation($this->client, $this->requestHandler->reveal(), $this->serializer); $publisher->invoke(); $subscriber->invoke(); @@ -209,19 +215,20 @@ public function testSnapshot() $time = time(); - $this->connection->createSession(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ]); - - $this->refreshOperation($this->client, $this->connection->reveal()); + ] + ); + $this->spannerClient->createSession( + null, + [ + 'name' => self::SESSION + ] + ); + $this->refreshOperation($this->client, $this->requestHandler->reveal(), $this->serializer); $res = $snippet->invoke('snapshot'); $this->assertInstanceOf(BatchSnapshot::class, $res->returnVal()); @@ -229,7 +236,7 @@ public function testSnapshot() public function testSnapshotFromString() { - $timestamp = new Timestamp(new \DateTime); + $timestamp = new Timestamp(new \DateTime()); $identifier = base64_encode(json_encode([ 'sessionName' => self::SESSION, diff --git a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php index d35ac96696a3..2ef49adafb54 100644 --- a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php +++ b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php @@ -27,12 +27,10 @@ use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\V1\SpannerClient; -use Prophecy\Argument; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Prophecy\PhpUnit\ProphecyTrait; /** @@ -42,15 +40,14 @@ class BatchSnapshotTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; - private $connection; + private $spannerClient; + private $serializer; private $session; private $time; private $snapshot; @@ -59,7 +56,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $sessData = SpannerClient::parseName(self::SESSION, 'session'); $this->session = $this->prophesize(Session::class); @@ -71,7 +68,7 @@ public function setUp(): void $this->time = time(); $this->snapshot = TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->connection->reveal(), false), + new Operation($this->requestHandler->reveal(), $this->serializer, false), $this->session->reveal(), [ 'id' => self::TRANSACTION, @@ -82,20 +79,23 @@ public function setUp(): void public function testClass() { - $this->connection->createSession(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->createSession( + null, + ['name' => self::SESSION] + ); + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ]); + 'readTimestamp' => \DateTime::createFromFormat( + 'U', + (string) $this->time + )->format(Timestamp::FORMAT) + ] + ); $client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->connection->reveal(), false), + new Operation($this->requestHandler->reveal(), $this->serializer, false), self::DATABASE ]); @@ -121,7 +121,7 @@ public function testSerializeSnapshot($index) public function provideSerializeIndex() { - return [[1],[2]]; + return [[1], [2]]; } public function testClose() @@ -129,7 +129,6 @@ public function testClose() $this->session->delete([]) ->shouldBeCalled(); - $this->snapshot->___setProperty('session', $this->session->reveal()); $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'close'); $snippet->addLocal('snapshot', $this->snapshot); @@ -142,15 +141,17 @@ public function testClose() */ public function testPartitionRead($method) { - $this->connection->$method(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->mockSendRequest( + SpannerClient::class, + $method, + null, + [ 'partitions' => [ ['partitionToken' => 'foo'] ] - ]); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + ] + ); + $this->refreshOperation($this->snapshot, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(BatchSnapshot::class, $method); $snippet->addLocal('snapshot', $this->snapshot); @@ -161,7 +162,7 @@ public function testPartitionRead($method) public function providePartitionMethods() { - return [['partitionRead'],['partitionQuery']]; + return [['partitionRead'], ['partitionQuery']]; } public function testExecutePartition() @@ -171,9 +172,9 @@ public function testExecutePartition() $opts = []; $partition = new QueryPartition($token, $sql, $opts); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator([ + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -187,9 +188,9 @@ public function testExecutePartition() ] ], 'values' => [0] - ])); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + ]) + ); + $this->refreshOperation($this->snapshot, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'executePartition'); $snippet->addLocal('snapshot', $this->snapshot); diff --git a/Spanner/tests/Snippet/Batch/PartitionSharedSnippetTestTrait.php b/Spanner/tests/Snippet/Batch/PartitionSharedSnippetTestTrait.php index 78e53d5cb95c..6339fd0cc9d0 100644 --- a/Spanner/tests/Snippet/Batch/PartitionSharedSnippetTestTrait.php +++ b/Spanner/tests/Snippet/Batch/PartitionSharedSnippetTestTrait.php @@ -42,7 +42,7 @@ public function testClassSerializeExamples($index) public function provideSerializeSnippetIndex() { - return [[1],[2]]; + return [[1], [2]]; } /** diff --git a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php index 37c89a92aac9..18ddd97e25ab 100644 --- a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php @@ -23,9 +23,7 @@ use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; -use Prophecy\Argument; /** * @group spanner @@ -35,12 +33,13 @@ class QueryPartitionTest extends SnippetTestCase { use GrpcTestTrait; use PartitionSharedSnippetTestTrait; - use StubCreationTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; + private $spannerClient; + private $serializer; private $className = QueryPartition::class; private $sql = 'SELECT 1=1'; private $time; @@ -49,34 +48,35 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->serializer = new Serializer(); $this->time = time(); $this->partition = new QueryPartition($this->token, $this->sql, $this->options); } public function testClass() { - $connection = $this->getConnStub(); - $connection->createSession(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->createSession( + null, + ['name' => self::SESSION] + ); + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ]); - $connection->partitionQuery(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + ] + ); + $this->spannerClient->partitionQuery( + null, + [ 'partitions' => [ ['partitionToken' => 'foo'] ] - ]); + ] + ); $client = TestHelpers::stub(BatchClient::class, [ - new Operation($connection->reveal(), false), + new Operation($this->requestHandler->reveal(), $this->serializer, false), self::DATABASE ]); diff --git a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php index c32e3af55516..e9b288e84ba2 100644 --- a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php @@ -24,9 +24,7 @@ use Google\Cloud\Spanner\Batch\ReadPartition; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; -use Prophecy\Argument; /** * @group spanner @@ -38,12 +36,13 @@ class ReadPartitionTest extends SnippetTestCase use PartitionSharedSnippetTestTrait { provideGetters as private getters; } - use StubCreationTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; + private $spannerClient; + private $serializer; private $className = ReadPartition::class; private $time; private $table; @@ -54,6 +53,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->serializer = new Serializer(); $this->time = time(); $this->table = 'table'; $this->keySet = new KeySet(['all' => true]); @@ -63,29 +63,31 @@ public function setUp(): void public function testClass() { - $connection = $this->getConnStub(); - $connection->createSession(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->createSession( + null, + ['name' => self::SESSION] + ); + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ]); - $connection->partitionRead(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + ] + ); + $this->spannerClient->partitionRead( + null, + [ 'partitions' => [ ['partitionToken' => 'foo'] ] - ]); + ] + ); $client = TestHelpers::stub(BatchClient::class, [ - new Operation($connection->reveal(), false), + new Operation($this->requestHandler->reveal(), $this->serializer, false), self::DATABASE + ], [ + 'operation' ]); $snippet = $this->snippetFromClass(ReadPartition::class); diff --git a/Spanner/tests/Snippet/BatchDmlResultTest.php b/Spanner/tests/Snippet/BatchDmlResultTest.php index f6945b8ca479..7ad12585b287 100644 --- a/Spanner/tests/Snippet/BatchDmlResultTest.php +++ b/Spanner/tests/Snippet/BatchDmlResultTest.php @@ -17,7 +17,6 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; @@ -27,7 +26,6 @@ use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -38,15 +36,17 @@ class BatchDmlResultTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; use TimeTrait; + private $spannerClient; + private $serializer; private $result; public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->serializer = new Serializer(); $this->result = new BatchDmlResult([ 'resultSets' => [ [ @@ -69,24 +69,22 @@ public function setUp(): void public function testClass() { - $connection = $this->getConnStub(); - $connection->executeBatchDml(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'resultSets' => [] - ]); - - $connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => 'ddfdfd' - ]); - - $connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => $this->formatTimeAsString(new \DateTime, 0) - ]); + $this->spannerClient->executeBatchDml( + null, + ['resultSets' => []] + ); + + $this->spannerClient->beginTransaction( + null, + ['id' => 'id'] + ); + + $this->spannerClient->commit( + null, + [ + 'commitTimestamp' => $this->formatTimeAsString(new \DateTime(), 0) + ] + ); $session = $this->prophesize(Session::class); $session->name()->willReturn( @@ -109,10 +107,9 @@ public function testClass() $instance->directedReadOptions()->willReturn([]); $database = TestHelpers::stub(Database::class, [ - $connection->reveal(), + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], 'test-project', 'projects/test-project/instances/my-instance/databases/my-database', $sessionPool->reveal() diff --git a/Spanner/tests/Snippet/BytesTest.php b/Spanner/tests/Snippet/BytesTest.php index ea268f6c1554..c75a55efb44f 100644 --- a/Spanner/tests/Snippet/BytesTest.php +++ b/Spanner/tests/Snippet/BytesTest.php @@ -17,10 +17,10 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Bytes; use Google\Cloud\Spanner\Database; -use Google\Cloud\Core\Testing\GrpcTestTrait; use Psr\Http\Message\StreamInterface; /** @@ -63,7 +63,7 @@ public function testGet() $res = $snippet->invoke('stream'); $this->assertInstanceOf(StreamInterface::class, $res->returnVal()); - $this->assertEquals(self::BYTES, (string)$res->returnVal()); + $this->assertEquals(self::BYTES, (string) $res->returnVal()); } public function testType() diff --git a/Spanner/tests/Snippet/CommitTimestampTest.php b/Spanner/tests/Snippet/CommitTimestampTest.php index 1f5dc9ce7f5a..98bc87449e33 100644 --- a/Spanner/tests/Snippet/CommitTimestampTest.php +++ b/Spanner/tests/Snippet/CommitTimestampTest.php @@ -19,12 +19,13 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Core\Timestamp; use Google\Cloud\Spanner\CommitTimestamp; use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use Prophecy\Argument; +use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\CommitRequest; /** * @group spanner @@ -33,12 +34,15 @@ class CommitTimestampTest extends SnippetTestCase { use GrpcTestTrait; - use StubCreationTrait; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; + private $spannerClient; + private $serializer; + public function setUp(): void { + $this->serializer = new Serializer(); $this->checkAndSkipGrpcTests(); } @@ -46,31 +50,49 @@ public function testClass() { $id = 'abc'; - $client = TestHelpers::stub(SpannerClient::class); - $conn = $this->getConnStub(); - $conn->createSession(Argument::any()) - ->willReturn([ - 'name' => self::SESSION - ]); + $client = new SpannerClient( + [['projectId' => 'my-project']], + ['requestHandler', 'serializer'] + ); + $this->GapicSpannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CreateSessionResponse(['name' => self::SESSION])); + $this->GapicSpannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); $mutation = [ 'insert' => [ 'table' => 'myTable', 'columns' => ['id', 'commitTimestamp'], - 'values' => [$id, CommitTimestamp::SPECIAL_VALUE] + 'values' => [[$id, CommitTimestamp::SPECIAL_VALUE]] ] ]; + $this->GapicSpannerClient->commit( + Argument::type(CommitRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(function ($args) use ($mutation) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals($message['mutations'][0], $mutation); + return true; + }, + [ + 'commitTimestamp' => \DateTime::createFromFormat('U', (string) time())->format(Timestamp::FORMAT) + ] + ); - $conn->commit(Argument::withEntry('mutations', [$mutation]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => \DateTime::createFromFormat('U', (string) time())->format(Timestamp::FORMAT) - ]); - - $client->___setProperty('connection', $conn->reveal()); $snippet = $this->snippetFromClass(CommitTimestamp::class); $snippet->addLocal('id', $id); $snippet->addLocal('spanner', $client); - $snippet->replace('$spanner = new SpannerClient();', ''); + $snippet->replace("\$spanner = new SpannerClient(['projectId' => 'my-project']);", ''); $snippet->invoke(); } diff --git a/Spanner/tests/Snippet/DatabaseTest.php b/Spanner/tests/Snippet/DatabaseTest.php index 486efd3922e0..18ab6970b552 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -17,16 +17,22 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\Iam\Iam; +use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\DropDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; @@ -35,11 +41,10 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -50,10 +55,8 @@ class DatabaseTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; @@ -61,7 +64,8 @@ class DatabaseTest extends SnippetTestCase const TRANSACTION = 'my-transaction'; const BACKUP = 'my-backup'; - private $connection; + private $spannerClient; + private $serializer; private $database; private $instance; @@ -86,24 +90,22 @@ public function setUp(): void ->willReturn(null); $sessionPool->clear()->willReturn(null); - $this->connection = $this->prophesize(ConnectionInterface::class); - $this->instance = TestHelpers::stub(Instance::class, [ - $this->connection->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], + $this->serializer = new Serializer(); + $this->instance = new Instance( + $this->requestHandler->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE - ], ['connection', 'lroConnection']); + ); $this->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->requestHandler->reveal(), + $this->serializer, $this->instance, - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['connection', 'operation', 'lroConnection']); + ], ['requestHandler', 'serializer', 'operation']); } public function testClass() @@ -135,11 +137,13 @@ public function testState() $snippet->addLocal('database', $this->database); $snippet->addUse(Database::class); - $this->connection->getDatabase(Argument::any()) - ->shouldBeCalledTimes(1) - ->WillReturn(['state' => Database::STATE_READY]); + $this->databaseAdminClient->getDatabase( + Argument::type(GetDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseResponse(['state' => Database::STATE_READY])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke(); $this->assertEquals('Database is ready!', $res->output()); @@ -153,17 +157,19 @@ public function testBackups() $snippet = $this->snippetFromMethod(Database::class, 'backups'); $snippet->addLocal('database', $this->database); - $this->connection->listBackups(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ + $this->databaseAdminClient->listBackups( + Argument::type(ListBackupsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn([ 'backups' => [ [ 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) ] ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + ] + ); $res = $snippet->invoke('backups'); @@ -179,13 +185,15 @@ public function testCreateBackup() $snippet = $this->snippetFromMethod(Database::class, 'createBackup'); $snippet->addLocal('database', $this->database); - $this->connection->createBackup(Argument::any(), Argument::any()) - ->shouldBeCalled() - ->willReturn(['name' => 'my-operations']); + $this->databaseAdminClient->createBackup( + Argument::type(CreateBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testName() @@ -204,11 +212,13 @@ public function testExists() $snippet = $this->snippetFromMethod(Database::class, 'exists'); $snippet->addLocal('database', $this->database); - $this->connection->getDatabase(Argument::any()) - ->shouldBeCalled() - ->willReturn(['statements' => []]); + $this->databaseAdminClient->getDatabase( + Argument::type(GetDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseResponse(['statements' => []])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke(); $this->assertEquals('Database exists!', $res->output()); @@ -224,11 +234,13 @@ public function testInfo() $snippet = $this->snippetFromMethod(Database::class, 'info'); $snippet->addLocal('database', $this->database); - $this->connection->getDatabase(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn($db); + $this->databaseAdminClient->getDatabase( + Argument::type(GetDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseResponse($db)); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); $this->assertEquals($db, $res->returnVal()); @@ -245,11 +257,15 @@ public function testReload() $snippet = $this->snippetFromMethod(Database::class, 'reload'); $snippet->addLocal('database', $this->database); - $this->connection->getDatabase(Argument::any()) - ->shouldBeCalledTimes(2) - ->willReturn($db); + $this->databaseAdminClient->getDatabase( + Argument::type(GetDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($db, + 2 + ); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); $this->assertEquals($db, $res->returnVal()); @@ -264,16 +280,16 @@ public function testCreate() $snippet = $this->snippetFromMethod(Database::class, 'create'); $snippet->addLocal('database', $this->database); - $this->connection->createDatabase(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->databaseAdminClient->createDatabase( + Argument::type(CreateDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } /** @@ -286,16 +302,16 @@ public function testRestore() $snippet->addLocal('database', $this->database); $snippet->addLocal('backup', $backup); - $this->connection->restoreDatabase(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->databaseAdminClient->restoreDatabase( + Argument::type(RestoreDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } /** @@ -306,13 +322,13 @@ public function testUpdateDdl() $snippet = $this->snippetFromMethod(Database::class, 'updateDdl'); $snippet->addLocal('database', $this->database); - $this->connection->updateDatabaseDdl(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->databaseAdminClient->updateDatabaseDdl( + Argument::type(UpdateDatabaseDdlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -325,13 +341,13 @@ public function testUpdateDdlBatch() $snippet = $this->snippetFromMethod(Database::class, 'updateDdlBatch'); $snippet->addLocal('database', $this->database); - $this->connection->updateDatabaseDdl(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->databaseAdminClient->updateDatabaseDdl( + Argument::type(UpdateDatabaseDdlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -344,10 +360,12 @@ public function testDrop() $snippet = $this->snippetFromMethod(Database::class, 'drop'); $snippet->addLocal('database', $this->database); - $this->connection->dropDatabase(Argument::any()) - ->shouldBeCalled(); + $this->databaseAdminClient->dropDatabase( + Argument::type(DropDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->database->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -365,13 +383,13 @@ public function testDdl() 'CREATE TABLE TestCases' ]; - $this->connection->getDatabaseDDL(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'statements' => $stmts - ]); + $this->databaseAdminClient->getDatabaseDdl( + Argument::type(GetDatabaseDdlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseDdlResponse(['statements' => $stmts])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('statements'); $this->assertEquals($stmts, $res->returnVal()); @@ -379,13 +397,12 @@ public function testDdl() public function testSnapshot() { - $this->connection->beginTransaction(Argument::any(), Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->beginTransaction( + null, + ['id' => self::TRANSACTION] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'snapshot'); $snippet->addLocal('database', $this->database); @@ -396,14 +413,15 @@ public function testSnapshot() public function testSnapshotReadTimestamp() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->beginTransaction( + null, + [ 'id' => self::TRANSACTION, - 'readTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + 'readTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'snapshot', 1); $snippet->addLocal('database', $this->database); @@ -414,32 +432,30 @@ public function testSnapshotReadTimestamp() public function testRunTransaction() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->beginTransaction( + null, + ['id' => self::TRANSACTION] + ); - $this->connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); - - $this->connection->rollback(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->commit( + null, + ['commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString()] + ); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->yieldRows([ + $this->spannerClient->executeStreamingSql( + null, + $this->yieldRows([ [ 'name' => 'loginCount', 'type' => Database::TYPE_INT64, 'value' => 0 ] - ])); + ]) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); + + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); @@ -452,20 +468,21 @@ public function testRunTransaction() public function testRunTransactionRollback() { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->commit(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->commit(Argument::cetera())->shouldNotBeCalled(); - $this->connection->rollback(Argument::any()) - ->shouldBeCalled(); + $this->mockSendRequest(SpannerClient::class, 'rollback', null, null, 1); - $this->connection->executeStreamingSql( - Argument::withEntry('transaction', ['begin' => ['readWrite' => []]]) - ) - ->shouldBeCalled() - ->willReturn($this->resultGeneratorData([ + $this->spannerClient->executeStreamingSql( + function ($args) { + $this->assertEquals( + $this->serializer->encodeMessage($args)['transaction']['begin']['readWrite'], + ['readLockMode' => 0] + ); + return true; + }, + $this->resultGeneratorData([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -481,9 +498,10 @@ public function testRunTransactionRollback() 'id' => self::TRANSACTION ] ] - ])); + ]) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); @@ -496,13 +514,12 @@ public function testRunTransactionRollback() public function testTransaction() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->beginTransaction( + null, + ['id' => self::TRANSACTION] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'transaction'); $snippet->addLocal('database', $this->database); @@ -512,37 +529,37 @@ public function testTransaction() public function testInsert() { - $this->connection->commit(Argument::that(function ($args) { - return isset($args['mutations'][0]['insert']); - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['insert']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'insert'); $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); } - public function testInsertBatch() { - $this->connection->commit(Argument::that(function ($args) { - if (!isset($args['mutations'][0]['insert'])) { - return false; - } - - if (!isset($args['mutations'][1]['insert'])) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['insert']) + && isset($message['mutations'][1]['insert']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'insertBatch'); $snippet->addLocal('database', $this->database); @@ -551,37 +568,37 @@ public function testInsertBatch() public function testUpdate() { - $this->connection->commit(Argument::that(function ($args) { - return isset($args['mutations'][0]['update']); - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['update']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'update'); $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); } - public function testUpdateBatch() { - $this->connection->commit(Argument::that(function ($args) { - if (!isset($args['mutations'][0]['update'])) { - return false; - } - - if (!isset($args['mutations'][1]['update'])) { - return false; - } + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['update']) + && isset($message['mutations'][1]['update']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - return true; - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'updateBatch'); $snippet->addLocal('database', $this->database); @@ -590,37 +607,37 @@ public function testUpdateBatch() public function testInsertOrUpdate() { - $this->connection->commit(Argument::that(function ($args) { - return isset($args['mutations'][0]['insertOrUpdate']); - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['insertOrUpdate']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'insertOrUpdate'); $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); } - public function testInsertOrUpdateBatch() { - $this->connection->commit(Argument::that(function ($args) { - if (!isset($args['mutations'][0]['insertOrUpdate'])) { - return false; - } + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['insertOrUpdate']) + && isset($message['mutations'][1]['insertOrUpdate']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - if (!isset($args['mutations'][1]['insertOrUpdate'])) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'insertOrUpdateBatch'); $snippet->addLocal('database', $this->database); @@ -629,37 +646,37 @@ public function testInsertOrUpdateBatch() public function testReplace() { - $this->connection->commit(Argument::that(function ($args) { - return isset($args['mutations'][0]['replace']); - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['replace']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'replace'); $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); } - public function testReplaceBatch() { - $this->connection->commit(Argument::that(function ($args) { - if (!isset($args['mutations'][0]['replace'])) { - return false; - } - - if (!isset($args['mutations'][1]['replace'])) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['replace']) + && isset($message['mutations'][1]['replace']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'replaceBatch'); $snippet->addLocal('database', $this->database); @@ -668,13 +685,17 @@ public function testReplaceBatch() public function testDelete() { - $this->connection->commit(Argument::that(function ($args) { - return isset($args['mutations'][0]['delete']); - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['mutations'][0]['delete']); + }, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'delete'); $snippet->addUse(KeySet::class); @@ -684,11 +705,12 @@ public function testDelete() public function testExecute() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator() + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'execute'); $snippet->addLocal('database', $this->database); @@ -699,37 +721,31 @@ public function testExecute() public function testExecuteWithParameterType() { - $this->connection->executeStreamingSql(Argument::that(function ($arg) { - if (!isset($arg['params'])) { - return false; - } - - if (!isset($arg['paramTypes'])) { - return false; - } - - if ($arg['paramTypes']['timestamp']['code'] !== Database::TYPE_TIMESTAMP) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn($this->resultGeneratorData([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'timestamp', - 'type' => [ - 'code' => Database::TYPE_TIMESTAMP + $this->spannerClient->executeStreamingSql( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['params']) + && isset($message['paramTypes']) + && $message['paramTypes']['timestamp']['code'] === Database::TYPE_TIMESTAMP; + }, + $this->resultGeneratorData([ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'timestamp', + 'type' => [ + 'code' => Database::TYPE_TIMESTAMP + ] ] ] ] - ] - ], - 'values' => [null] - ])); + ], + 'values' => [null] + ]) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'execute', 1); $snippet->addLocal('database', $this->database); @@ -740,44 +756,36 @@ public function testExecuteWithParameterType() public function testExecuteWithEmptyArray() { - $this->connection->executeStreamingSql(Argument::that(function ($arg) { - if (!isset($arg['params'])) { - return false; - } - - if (!isset($arg['paramTypes'])) { - return false; - } - - if ($arg['paramTypes']['emptyArrayOfIntegers']['code'] !== Database::TYPE_ARRAY) { - return false; - } - - if ($arg['paramTypes']['emptyArrayOfIntegers']['arrayElementType']['code'] !== Database::TYPE_INT64) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn($this->resultGeneratorData([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'numbers', - 'type' => [ - 'code' => Database::TYPE_ARRAY, - 'arrayElementType' => [ - 'code' => Database::TYPE_INT64 + $this->spannerClient->executeStreamingSql( + function ($args) { + $message = $this->serializer->encodeMessage($args); + return isset($message['params']) + && isset($message['paramTypes']) + && $message['paramTypes']['emptyArrayOfIntegers']['code'] === Database::TYPE_ARRAY + && $message['paramTypes']['emptyArrayOfIntegers']['arrayElementType']['code'] + === Database::TYPE_INT64; + }, + $this->resultGeneratorData([ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'numbers', + 'type' => [ + 'code' => Database::TYPE_ARRAY, + 'arrayElementType' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] ] - ] - ], - 'values' => [[]] - ])); + ], + 'values' => [[]] + ]) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'execute', 2); $snippet->addLocal('database', $this->database); @@ -788,11 +796,12 @@ public function testExecuteWithEmptyArray() public function testExecuteBeginSnapshot() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator(false, self::TRANSACTION) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'execute', 5); $snippet->addLocal('database', $this->database); @@ -804,11 +813,12 @@ public function testExecuteBeginSnapshot() public function testExecuteBeginTransaction() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator(false, self::TRANSACTION) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'execute', 6); $snippet->addLocal('database', $this->database); @@ -820,17 +830,17 @@ public function testExecuteBeginTransaction() public function testExecutePartitionedUpdate() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->beginTransaction( + null, + ['id' => self::TRANSACTION] + ); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(true)); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator(true) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'executePartitionedUpdate'); $snippet->addLocal('database', $this->database); @@ -841,11 +851,12 @@ public function testExecutePartitionedUpdate() public function testRead() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->streamingRead( + null, + $this->resultGenerator() + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'read'); $snippet->addLocal('database', $this->database); @@ -856,11 +867,12 @@ public function testRead() public function testReadWithSnapshot() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); + $this->spannerClient->streamingRead( + null, + $this->resultGenerator(false, self::TRANSACTION) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'read', 1); $snippet->addLocal('database', $this->database); @@ -872,11 +884,12 @@ public function testReadWithSnapshot() public function testReadWithTransaction() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); + $this->spannerClient->streamingRead( + null, + $this->resultGenerator(false, self::TRANSACTION) + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Database::class, 'read', 2); $snippet->addLocal('database', $this->database); @@ -909,7 +922,7 @@ public function testIam() $snippet->addLocal('database', $this->database); $res = $snippet->invoke('iam'); - $this->assertInstanceOf(Iam::class, $res->returnVal()); + $this->assertInstanceOf(IamManager::class, $res->returnVal()); } public function testResumeOperation() @@ -919,7 +932,7 @@ public function testResumeOperation() $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); $this->assertEquals('foo', $res->returnVal()->name()); } @@ -928,21 +941,17 @@ public function testLongRunningOperations() $snippet = $this->snippetFromMethod(Database::class, 'longRunningOperations'); $snippet->addLocal('database', $this->database); - $lroConnection = $this->prophesize(LongRunningConnectionInterface::class); - $lroConnection->operations(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'operations' => [ - [ - 'name' => 'foo' - ] - ] - ]); + $this->requestHandler + ->sendRequest( + Argument::any(), + 'listOperations', + Argument::cetera() + ) + ->willReturn([$this->getOperationResponseMock()]); - $this->database->___setProperty('lroConnection', $lroConnection->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); } } diff --git a/Spanner/tests/Snippet/DateTest.php b/Spanner/tests/Snippet/DateTest.php index d73aa7dc9ab0..54971ee2b429 100644 --- a/Spanner/tests/Snippet/DateTest.php +++ b/Spanner/tests/Snippet/DateTest.php @@ -17,10 +17,10 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner @@ -36,7 +36,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->dt = new \DateTimeImmutable; + $this->dt = new \DateTimeImmutable(); $this->date = new Date($this->dt); } diff --git a/Spanner/tests/Snippet/DurationTest.php b/Spanner/tests/Snippet/DurationTest.php deleted file mode 100644 index 58b0fab6a1d4..000000000000 --- a/Spanner/tests/Snippet/DurationTest.php +++ /dev/null @@ -1,85 +0,0 @@ -checkAndSkipGrpcTests(); - - $this->duration = new Duration(self::SECONDS, self::NANOS); - } - - public function testClass() - { - $snippet = $this->snippetFromClass(Duration::class); - $res = $snippet->invoke('duration'); - $this->assertInstanceOf(Duration::class, $res->returnVal()); - } - - public function testClassCast() - { - $snippet = $this->snippetFromClass(Duration::class, 1); - $snippet->addLocal('duration', $this->duration); - - $res = $snippet->invoke(); - $this->assertEquals($this->duration->formatAsString(), $res->output()); - } - - public function testGet() - { - $snippet = $this->snippetFromMethod(Duration::class, 'get'); - $snippet->addLocal('duration', $this->duration); - - $res = $snippet->invoke('res'); - $this->assertEquals($this->duration->get(), $res->returnVal()); - } - - public function testType() - { - $snippet = $this->snippetFromMethod(Duration::class, 'type'); - $snippet->addLocal('duration', $this->duration); - - $res = $snippet->invoke(); - $this->assertEquals(Duration::TYPE, $res->output()); - } - - public function testFormatAsString() - { - $snippet = $this->snippetFromMethod(Duration::class, 'formatAsString'); - $snippet->addLocal('duration', $this->duration); - - $res = $snippet->invoke(); - $this->assertEquals($this->duration->formatAsString(), $res->output()); - } -} diff --git a/Spanner/tests/Snippet/InstanceConfigurationTest.php b/Spanner/tests/Snippet/InstanceConfigurationTest.php index af867e2b8a8c..d2095a60b091 100644 --- a/Spanner/tests/Snippet/InstanceConfigurationTest.php +++ b/Spanner/tests/Snippet/InstanceConfigurationTest.php @@ -17,14 +17,15 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; +use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\InstanceConfiguration; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -36,31 +37,33 @@ class InstanceConfigurationTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const CONFIG = 'regional-europe-west'; - private $connection; + private $spannerClient; + private $serializer; private $config; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->config = TestHelpers::stub(InstanceConfiguration::class, [ - $this->connection->reveal(), + $this->serializer = new Serializer(); + $this->config = new InstanceConfiguration( + $this->requestHandler->reveal(), + $this->serializer, self::PROJECT, self::CONFIG, [], - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - ], ['connection', 'lroConnection']); + ); } public function testClass() { $snippet = $this->snippetFromClass(InstanceConfiguration::class); + $snippet->addLocal('projectId', self::PROJECT); + $res = $snippet->invoke('configuration'); $this->assertInstanceOf(InstanceConfiguration::class, $res->returnVal()); @@ -73,24 +76,34 @@ public function testClass() public function testCreate() { $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'create'); - $this->connection->createInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn(['name' => 'operations/foo']); - $dummyConnection = $this->connection->reveal(); + $this->instanceAdminClient->createInstanceConfig( + Argument::type(CreateInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CreateInstanceConfigResponse($this->getOperationResponseMock(),)); + $baseConfig = new InstanceConfiguration( - $dummyConnection, + $this->requestHandler->reveal(), + $this->serializer, self::PROJECT, self::CONFIG, - [], - $this->prophesize(LongRunningConnectionInterface::class)->reveal() + [] + ); + $this->config->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); + $this->config->___setProperty( + 'serializer', + $this->serializer ); - $this->config->___setProperty('connection', $dummyConnection); $snippet->addLocal('baseConfig', $baseConfig); $snippet->addLocal('options', []); $snippet->addLocal('instanceConfig', $this->config); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testUpdate() @@ -98,13 +111,16 @@ public function testUpdate() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'update'); $snippet->addLocal('instanceConfig', $this->config); - $this->connection->updateInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->instanceAdminClient->updateInstanceConfig( + Argument::type(UpdateInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + + $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); + $this->config->___setProperty('serializer', $this->serializer); - $this->config->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -113,10 +129,10 @@ public function testDelete() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'delete'); $snippet->addLocal('instanceConfig', $this->config); - $this->connection->deleteInstanceConfig(Argument::any()) - ->shouldBeCalled(); + $this->mockSendRequest(InstanceAdminClient::class, 'deleteInstanceConfig', null, null); - $this->config->___setProperty('connection', $this->connection->reveal()); + $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); + $this->config->___setProperty('serializer', $this->serializer); $snippet->invoke(); } @@ -139,11 +155,10 @@ public function testInfo() 'displayName' => self::CONFIG ]; - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn($info); + $this->mockSendRequest(InstanceAdminClient::class, 'getInstanceConfig', null, $info); - $this->config->___setProperty('connection', $this->connection->reveal()); + $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); + $this->config->___setProperty('serializer', $this->serializer); $res = $snippet->invoke('info'); $this->assertEquals($info, $res->returnVal()); @@ -154,14 +169,19 @@ public function testExists() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'exists'); $snippet->addLocal('configuration', $this->config); - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willReturn([ 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG), 'displayName' => self::CONFIG - ]); + ] + ); - $this->config->___setProperty('connection', $this->connection->reveal()); + $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); + $this->config->___setProperty('serializer', $this->serializer); $res = $snippet->invoke(); $this->assertEquals('Configuration exists!', $res->output()); @@ -177,11 +197,10 @@ public function testReload() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'reload'); $snippet->addLocal('configuration', $this->config); - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn($info); + $this->mockSendRequest(InstanceAdminClient::class, 'getInstanceConfig', null, $info); - $this->config->___setProperty('connection', $this->connection->reveal()); + $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); + $this->config->___setProperty('serializer', $this->serializer); $res = $snippet->invoke('info'); $this->assertEquals($info, $res->returnVal()); diff --git a/Spanner/tests/Snippet/InstanceTest.php b/Spanner/tests/Snippet/InstanceTest.php index 71cb7897e01c..6f9dd620ecd2 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -17,21 +17,25 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\Iam\Iam; +use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; +use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceRequest; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\InstanceConfiguration; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -43,7 +47,6 @@ class InstanceTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const INSTANCE = 'my-instance'; @@ -51,26 +54,27 @@ class InstanceTest extends SnippetTestCase const BACKUP = 'my-backup'; const OPERATION = 'my-operation'; - private $connection; + private $spannerClient; + private $serializer; private $instance; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->instance = TestHelpers::stub(Instance::class, [ - $this->connection->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], + $this->serializer = new Serializer(); + $this->instance = new Instance( + $this->requestHandler->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE - ], ['connection', 'lroConnection']); + ); } public function testClass() { $snippet = $this->snippetFromClass(Instance::class); + $snippet->addLocal('projectId', self::PROJECT); $res = $snippet->invoke('instance'); $this->assertInstanceOf(Instance::class, $res->returnVal()); $this->assertEquals( @@ -91,14 +95,19 @@ public function testCreate() $snippet->addLocal('configuration', $config->reveal()); $snippet->addLocal('instance', $this->instance); - $this->connection->createInstance(Argument::any()) - ->shouldBeCalled() - ->willReturn(['name' => 'operations/foo']); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->createInstance( + Argument::type(CreateInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testName() @@ -115,12 +124,17 @@ public function testInfo() $snippet = $this->snippetFromMethod(Instance::class, 'info'); $snippet->addLocal('instance', $this->instance); - $this->connection->getInstance(Argument::any()) - ->shouldBeCalled() - ->willReturn(['nodeCount' => 1]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::type(GetInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetInstanceResponse(['nodeCount' => 1])); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke(); $this->assertEquals('1', $res->output()); } @@ -130,12 +144,17 @@ public function testExists() $snippet = $this->snippetFromMethod(Instance::class, 'exists'); $snippet->addLocal('instance', $this->instance); - $this->connection->getInstance(Argument::any()) - ->shouldBeCalled() - ->willReturn(['foo' => 'bar']); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::type(GetInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetInstanceResponse(['foo' => 'bar'])); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke(); $this->assertEquals('Instance exists!', $res->output()); } @@ -145,12 +164,17 @@ public function testReload() $snippet = $this->snippetFromMethod(Instance::class, 'reload'); $snippet->addLocal('instance', $this->instance); - $this->connection->getInstance(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn(['nodeCount' => 1]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::type(GetInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetInstanceResponse(['nodeCount' => 1])); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('info'); $info = $this->instance->info(); $this->assertEquals($info, $res->returnVal()); @@ -162,12 +186,17 @@ public function testState() $snippet->addLocal('instance', $this->instance); $snippet->addUse(Instance::class); - $this->connection->getInstance(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn(['state' => Instance::STATE_READY]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::type(GetInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetInstanceResponse(['state' => Instance::STATE_READY])); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke(); $this->assertEquals('Instance is ready!', $res->output()); } @@ -177,13 +206,17 @@ public function testUpdate() $snippet = $this->snippetFromMethod(Instance::class, 'update'); $snippet->addLocal('instance', $this->instance); - $this->connection->updateInstance(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->instanceAdminClient->updateInstance( + Argument::type(UpdateInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $snippet->invoke(); } @@ -192,10 +225,12 @@ public function testDelete() $snippet = $this->snippetFromMethod(Instance::class, 'delete'); $snippet->addLocal('instance', $this->instance); - $this->connection->deleteInstance(Argument::any()) - ->shouldBeCalled(); + $this->mockSendRequest(InstanceAdminClient::class, 'deleteInstance', null, null); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $snippet->invoke(); } @@ -204,16 +239,19 @@ public function testCreateDatabase() $snippet = $this->snippetFromMethod(Instance::class, 'createDatabase'); $snippet->addLocal('instance', $this->instance); - $this->connection->createDatabase(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createDatabase( + Argument::type(CreateDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testCreateDatabaseFromBackup() @@ -223,16 +261,19 @@ public function testCreateDatabaseFromBackup() $snippet->addLocal('instance', $this->instance); $snippet->addLocal('backup', $backup); - $this->connection->restoreDatabase(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->restoreDatabase( + Argument::type(RestoreDatabaseRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testDatabase() @@ -250,18 +291,28 @@ public function testDatabases() $snippet = $this->snippetFromMethod(Instance::class, 'databases'); $snippet->addLocal('instance', $this->instance); - $this->connection->listDatabases(Argument::any()) - ->shouldBeCalled() + $this->databaseAdminClient->listDatabases( + Argument::type(ListDatabasesRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willReturn([ 'databases' => [ [ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + 'name' => DatabaseAdminClient::databaseName( + self::PROJECT, + self::INSTANCE, + self::DATABASE + ) ] ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + ] + ); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('databases'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -283,18 +334,28 @@ public function testBackups() $snippet = $this->snippetFromMethod(Instance::class, 'backups'); $snippet->addLocal('instance', $this->instance); - $this->connection->listBackups(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ + $this->databaseAdminClient->listBackups( + Argument::type(ListBackupsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn([ 'backups' => [ [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) + 'name' => DatabaseAdminClient::backupName( + self::PROJECT, + self::INSTANCE, + self::BACKUP + ) ] ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + ] + ); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('backups'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -304,7 +365,7 @@ public function testBackups() public function testBackupOperations() { $backupOperationName = sprintf( - "%s/operations/%s", + '%s/operations/%s', DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP), self::OPERATION ); @@ -312,28 +373,34 @@ public function testBackupOperations() $snippet = $this->snippetFromMethod(Instance::class, 'backupOperations'); $snippet->addLocal('instance', $this->instance); - $this->connection->listBackupOperations(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ + $this->databaseAdminClient->listBackupOperations( + Argument::type(ListBackupOperationsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn([ 'operations' => [ [ 'name' => $backupOperationName ] ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + ] + ); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('backupOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()->current()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()->current()); } public function testDatabaseOperations() { $databaseOperationName = sprintf( - "%s/operations/%s", + '%s/operations/%s', DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), self::OPERATION ); @@ -341,22 +408,28 @@ public function testDatabaseOperations() $snippet = $this->snippetFromMethod(Instance::class, 'databaseOperations'); $snippet->addLocal('instance', $this->instance); - $this->connection->listDatabaseOperations(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ + $this->databaseAdminClient->listDatabaseOperations( + Argument::type(ListDatabaseOperationsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn([ 'operations' => [ [ 'name' => $databaseOperationName ] ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + ] + ); + $this->instance->___setProperty( + 'requestHandler', + $this->requestHandler->reveal() + ); $res = $snippet->invoke('databaseOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()->current()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()->current()); } public function testIam() @@ -365,7 +438,7 @@ public function testIam() $snippet->addLocal('instance', $this->instance); $res = $snippet->invoke('iam'); - $this->assertInstanceOf(Iam::class, $res->returnVal()); + $this->assertInstanceOf(IamManager::class, $res->returnVal()); } public function testResumeOperation() @@ -375,7 +448,7 @@ public function testResumeOperation() $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); $this->assertEquals('foo', $res->returnVal()->name()); } @@ -384,22 +457,18 @@ public function testLongRunningOperations() $snippet = $this->snippetFromMethod(Instance::class, 'longRunningOperations'); $snippet->addLocal('instance', $this->instance); - $lroConnection = $this->prophesize(LongRunningConnectionInterface::class); - $lroConnection->operations(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'operations' => [ - [ - 'name' => 'foo' - ] - ] - ]); - - $this->instance->___setProperty('lroConnection', $lroConnection->reveal()); + $this->requestHandler + ->sendRequest( + Argument::any(), + 'listOperations', + Argument::cetera() + ) + ->willReturn([$this->getOperationResponseMock()]); + $this->instance->___setProperty('requestHandler', $this->requestHandler->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); } public function testDatabaseWithDatabaseRole() diff --git a/Spanner/tests/Snippet/KeyRangeTest.php b/Spanner/tests/Snippet/KeyRangeTest.php index 6bfe45eb6c7c..905408e022d8 100644 --- a/Spanner/tests/Snippet/KeyRangeTest.php +++ b/Spanner/tests/Snippet/KeyRangeTest.php @@ -17,9 +17,9 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\KeyRange; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner @@ -34,7 +34,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->range = new KeyRange; + $this->range = new KeyRange(); } public function testClass() diff --git a/Spanner/tests/Snippet/KeySetTest.php b/Spanner/tests/Snippet/KeySetTest.php index 6134a7f730a9..784d9a948211 100644 --- a/Spanner/tests/Snippet/KeySetTest.php +++ b/Spanner/tests/Snippet/KeySetTest.php @@ -17,10 +17,10 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner diff --git a/Spanner/tests/Snippet/NumericTest.php b/Spanner/tests/Snippet/NumericTest.php index f7e4fe753cc9..921d92143080 100644 --- a/Spanner/tests/Snippet/NumericTest.php +++ b/Spanner/tests/Snippet/NumericTest.php @@ -19,7 +19,6 @@ use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Numeric; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner diff --git a/Spanner/tests/Snippet/ResultTest.php b/Spanner/tests/Snippet/ResultTest.php index c00cf77f3d2e..4644241572cd 100644 --- a/Spanner/tests/Snippet/ResultTest.php +++ b/Spanner/tests/Snippet/ResultTest.php @@ -17,13 +17,13 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Snapshot; use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Core\Testing\GrpcTestTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Snippet/Session/CacheSessionPoolTest.php b/Spanner/tests/Snippet/Session/CacheSessionPoolTest.php index e54b6e6e646b..140880cc0373 100644 --- a/Spanner/tests/Snippet/Session/CacheSessionPoolTest.php +++ b/Spanner/tests/Snippet/Session/CacheSessionPoolTest.php @@ -35,7 +35,7 @@ public function testClass() $snippet = $this->snippetFromClass(CacheSessionPool::class); $snippet->replace('$cache =', '//$cache ='); - $snippet->addLocal('cache', new MemoryCacheItemPool); + $snippet->addLocal('cache', new MemoryCacheItemPool()); $res = $snippet->invoke('database'); $this->assertInstanceOf(Database::class, $res->returnVal()); } @@ -48,7 +48,7 @@ public function testClassLabels() $snippet = $this->snippetFromClass(CacheSessionPool::class, 1); $snippet->replace('$cache =', '//$cache ='); - $snippet->addLocal('cache', new MemoryCacheItemPool); + $snippet->addLocal('cache', new MemoryCacheItemPool()); $res = $snippet->invoke(); } @@ -60,7 +60,7 @@ public function testClassWithDatabaseRole() $snippet = $this->snippetFromClass(CacheSessionPool::class, 2); $snippet->replace('$cache =', '//$cache ='); - $snippet->addLocal('cache', new MemoryCacheItemPool); + $snippet->addLocal('cache', new MemoryCacheItemPool()); $res = $snippet->invoke('database'); $this->assertInstanceOf(Database::class, $res->returnVal()); } diff --git a/Spanner/tests/Snippet/SnapshotTest.php b/Spanner/tests/Snippet/SnapshotTest.php index f66b13171519..5dd38ddea676 100644 --- a/Spanner/tests/Snippet/SnapshotTest.php +++ b/Spanner/tests/Snippet/SnapshotTest.php @@ -24,8 +24,6 @@ use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Prophecy\PhpUnit\ProphecyTrait; @@ -35,20 +33,19 @@ class SnapshotTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; const TRANSACTION = 'my-transaction'; - private $connection; + private $spannerClient; + private $serializer; private $snapshot; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $operation = $this->prophesize(Operation::class); $session = $this->prophesize(Session::class); @@ -57,7 +54,7 @@ public function setUp(): void $session->reveal(), [ 'id' => self::TRANSACTION, - 'readTimestamp' => new Timestamp(new \DateTime) + 'readTimestamp' => new Timestamp(new \DateTime()) ] ], ['operation']); } diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index 564f75698cc1..8e3b6e76cb78 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -17,31 +17,30 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Bytes; use Google\Cloud\Spanner\CommitTimestamp; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\InstanceConfiguration; use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Numeric; +use Google\Cloud\Spanner\PgJsonb; use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\PgOid; -use Google\Cloud\Spanner\PgJsonb; -use Prophecy\Argument; +use Google\Cloud\Spanner\SpannerClient; +use Google\Cloud\Spanner\Timestamp; +use Google\Protobuf\Duration; /** * @group spanner @@ -49,22 +48,24 @@ class SpannerClientTest extends SnippetTestCase { use GrpcTestTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const CONFIG = 'foo'; const INSTANCE = 'my-instance'; private $client; - private $connection; + private $spannerClient; + private $serializer; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->client = TestHelpers::stub(SpannerClient::class); - $this->client->___setProperty('connection', $this->connection->reveal()); + $this->serializer = new Serializer(); + $this->client = new SpannerClient( + [['projectId' => self::PROJECT]], + ['requestHandler', 'serializer'] + ); } public function testClass() @@ -87,16 +88,18 @@ public function testBatch() */ public function testInstanceConfigurations() { - $this->connection->listInstanceConfigs(Argument::any()) - ->shouldBeCalled() + $this->instanceAdminClient->listInstanceConfigs( + Argument::type(ListInstanceConfigsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willReturn([ 'instanceConfigs' => [ ['name' => 'projects/my-awesome-projects/instanceConfigs/foo'], ['name' => 'projects/my-awesome-projects/instanceConfigs/bar'], ] - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ] + ); $snippet = $this->snippetFromMethod(SpannerClient::class, 'instanceConfigurations'); $snippet->addLocal('spanner', $this->client); @@ -136,14 +139,15 @@ public function testCreateInstance() $snippet->addLocal('spanner', $this->client); $snippet->addLocal('configuration', $this->client->instanceConfiguration(self::CONFIG)); - $this->connection->createInstance(Argument::any()) - ->shouldBeCalled() - ->willReturn(['name' => 'operations/foo']); - - $this->client->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->createInstance( + Argument::type(CreateInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } /** @@ -170,16 +174,18 @@ public function testInstances() $snippet = $this->snippetFromMethod(SpannerClient::class, 'instances'); $snippet->addLocal('spanner', $this->client); - $this->connection->listInstances(Argument::any()) - ->shouldBeCalled() + $this->instanceAdminClient->listInstances( + Argument::type(ListInstancesRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willReturn([ 'instances' => [ ['name' => InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)], ['name' => InstanceAdminClient::instanceName(self::PROJECT, 'bar')] ] - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ] + ); $res = $snippet->invoke('instances'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -273,7 +279,7 @@ public function testResumeOperation() $snippet->addLocal('operationName', 'operations/foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testEmulator() diff --git a/Spanner/tests/Snippet/StructTypeTest.php b/Spanner/tests/Snippet/StructTypeTest.php index bbd1f7bc08ef..2b0391935dfe 100644 --- a/Spanner/tests/Snippet/StructTypeTest.php +++ b/Spanner/tests/Snippet/StructTypeTest.php @@ -17,7 +17,6 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; @@ -28,8 +27,6 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructType; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -40,15 +37,14 @@ class StructTypeTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; const INSTANCE = 'my-instance'; - private $connection; + private $spannerClient; + private $serializer; private $database; private $type; @@ -76,18 +72,17 @@ public function setUp(): void $sessionPool->setDatabase(Argument::any()) ->willReturn(null); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $this->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation']); + ], ['operation', 'requestHandler', 'serializer']); - $this->type = new StructType; + $this->type = new StructType(); } public function testExecuteStruct() @@ -96,12 +91,16 @@ public function testExecuteStruct() [ 'name' => 'firstName', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ 'name' => 'lastName', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ] ]; @@ -111,29 +110,24 @@ public function testExecuteStruct() 'Testuser' ]; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', 'SELECT @userStruct.firstName, @userStruct.lastName'), - Argument::withEntry('params', [ - 'userStruct' => $values - ]), - Argument::withEntry('paramTypes', [ - 'userStruct' => [ - 'code' => Database::TYPE_STRUCT, - 'structType' => [ + $this->spannerClient->executeStreamingSql( + function ($args) use ($fields, $values) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals('SELECT @userStruct.firstName, @userStruct.lastName', $args->getSql()); + $this->assertEquals($message['params']['userStruct'], $values); + $this->assertEquals($message['paramTypes']['userStruct']['structType']['fields'], $fields); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ 'fields' => $fields ] - ] + ], + 'values' => $values ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], - 'values' => $values - ])); - - $this->refreshOperation($this->database, $this->connection->reveal()); + ); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromClass(StructType::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); @@ -177,7 +171,7 @@ public function testAddComplex() [ 'name' => 'customer', 'type' => Database::TYPE_STRUCT, - 'child' => (new StructType) + 'child' => (new StructType()) ->add('name', Database::TYPE_STRING) ->add('phone', Database::TYPE_STRING) ->add('email', Database::TYPE_STRING) diff --git a/Spanner/tests/Snippet/StructValueTest.php b/Spanner/tests/Snippet/StructValueTest.php index 64eab73f2f23..24601f3c8e86 100644 --- a/Spanner/tests/Snippet/StructValueTest.php +++ b/Spanner/tests/Snippet/StructValueTest.php @@ -17,7 +17,6 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; @@ -27,8 +26,6 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructValue; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -39,15 +36,14 @@ class StructValueTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; const INSTANCE = 'my-instance'; - private $connection; + private $spannerClient; + private $serializer; private $database; private $value; @@ -75,18 +71,17 @@ public function setUp(): void $sessionPool->setDatabase(Argument::any()) ->willReturn(null); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $this->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() ], ['operation']); - $this->value = new StructValue; + $this->value = new StructValue(); } public function testConstructor() @@ -95,16 +90,23 @@ public function testConstructor() [ 'name' => 'foo', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ 'name' => 'foo', 'type' => [ - 'code' => Database::TYPE_INT64 + 'code' => Database::TYPE_INT64, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ + 'name' => '', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ] ]; @@ -115,29 +117,40 @@ public function testConstructor() 'this field is unnamed' ]; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', 'SELECT * FROM UNNEST(ARRAY(SELECT @structParam))'), - Argument::withEntry('params', [ - 'structParam' => $values - ]), - Argument::withEntry('paramTypes', [ - 'structParam' => [ - 'code' => Database::TYPE_STRUCT, - 'structType' => [ + $this->spannerClient->executeStreamingSql( + function ($args) use ($values, $fields) { + $this->assertEquals( + $args->getSql(), + 'SELECT * FROM UNNEST(ARRAY(SELECT @structParam))' + ); + $message = $this->serializer->encodeMessage($args); + $this->assertEquals($message['params'], ['structParam' => $values]); + $this->assertEquals( + $message['paramTypes'], + [ + 'structParam' => [ + 'code' => Database::TYPE_STRUCT, + 'structType' => [ + 'fields' => $fields + ], + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' + ] + ] + ); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ 'fields' => $fields ] - ] + ], + 'values' => $values ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], - 'values' => $values - ])); + ); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromClass(StructValue::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); diff --git a/Spanner/tests/Snippet/TimestampTest.php b/Spanner/tests/Snippet/TimestampTest.php index 24be6f0a7256..f29869bf2b77 100644 --- a/Spanner/tests/Snippet/TimestampTest.php +++ b/Spanner/tests/Snippet/TimestampTest.php @@ -17,10 +17,10 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner @@ -35,7 +35,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->dt = new \DateTime; + $this->dt = new \DateTime(); $this->timestamp = new Timestamp($this->dt); } @@ -52,7 +52,7 @@ public function testClassCast() $snippet->addLocal('timestamp', $this->timestamp); $res = $snippet->invoke(); - $this->assertEquals((string)$this->timestamp, $res->output()); + $this->assertEquals((string) $this->timestamp, $res->output()); } public function testGet() diff --git a/Spanner/tests/Snippet/TransactionTest.php b/Spanner/tests/Snippet/TransactionTest.php index 48027c6c44c8..31b747eae17e 100644 --- a/Spanner/tests/Snippet/TransactionTest.php +++ b/Spanner/tests/Snippet/TransactionTest.php @@ -27,11 +27,10 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\StructType; use Google\Cloud\Spanner\StructValue; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CommitResponse\CommitStats; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -42,21 +41,20 @@ class TransactionTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; const TRANSACTION = 'my-transaction'; - private $connection; + private $spannerClient; + private $serializer; private $transaction; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $operation = $this->prophesize(Operation::class); $session = $this->prophesize(Session::class); $session->info() @@ -100,11 +98,12 @@ public function testClassReturnTransaction() public function testExecute() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator() + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMagicMethod(Transaction::class, 'execute'); $snippet->addLocal('transaction', $this->transaction); @@ -115,11 +114,12 @@ public function testExecute() public function testExecuteUpdate() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(true)); + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator(true) + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate'); $snippet->addLocal('transaction', $this->transaction); @@ -131,46 +131,42 @@ public function testExecuteUpdate() public function testExecuteUpdateWithStruct() { $expectedSql = "UPDATE Posts SET title = 'Updated Title' WHERE " . - "STRUCT(Title, Content) = @post"; + 'STRUCT<Title STRING, Content STRING>(Title, Content) = @post'; $expectedParams = [ - 'post' => ["Updated Title", "Sample Content"] + 'post' => ['Updated Title', 'Sample Content'] ]; $expectedStructData = [ [ - "name" => "Title", - "type" => [ - "code" => Database::TYPE_STRING + 'name' => 'Title', + 'type' => [ + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ - "name" => "Content", - "type" => [ - "code" => Database::TYPE_STRING + 'name' => 'Content', + 'type' => [ + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ] ]; - $this->connection->executeStreamingSql( - Argument::allOf( - Argument::withEntry('sql', $expectedSql), - Argument::withEntry('params', $expectedParams), - Argument::withEntry( - 'paramTypes', - Argument::withEntry( - 'post', - Argument::withEntry( - 'structType', - Argument::withEntry('fields', Argument::is($expectedStructData)) - ) - ) - ) - ) - ) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(true)); + $this->spannerClient->executeStreamingSql( + function ($args) use ($expectedSql, $expectedParams, $expectedStructData) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals($expectedSql, $args->getSql()); + $this->assertEquals($message['params'], $expectedParams); + $this->assertEquals($message['paramTypes']['post']['structType']['fields'], $expectedStructData); + return true; + }, + $this->resultGenerator(true) + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate', 1); $snippet->addUse(Database::class); @@ -185,9 +181,9 @@ public function testExecuteUpdateWithStruct() public function testExecuteUpdateBatch() { - $this->connection->executeBatchDml(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->executeBatchDml( + null, + [ 'resultSets' => [ [ 'stats' => [ @@ -195,9 +191,14 @@ public function testExecuteUpdateBatch() ] ] ] - ]); + ] + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdateBatch'); $snippet->addLocal('transaction', $this->transaction); @@ -208,17 +209,22 @@ public function testExecuteUpdateBatch() public function testExecuteUpdateBatchError() { - $this->connection->executeBatchDml(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->executeBatchDml( + null, + [ 'resultSets' => [], 'status' => [ 'code' => 3, 'message' => 'foo' ] - ]); + ] + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdateBatch'); $snippet->addLocal('transaction', $this->transaction); @@ -229,11 +235,16 @@ public function testExecuteUpdateBatchError() public function testRead() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->streamingRead( + null, + $this->resultGenerator() + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMagicMethod(Transaction::class, 'read'); $snippet->addLocal('transaction', $this->transaction); @@ -256,7 +267,11 @@ public function testInsert() $snippet = $this->snippetFromMethod(Transaction::class, 'insert'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -264,13 +279,16 @@ public function testInsert() $this->assertArrayHasKey('insert', $mutations[0]); } - public function testInsertBatch() { $snippet = $this->snippetFromMethod(Transaction::class, 'insertBatch'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -283,7 +301,11 @@ public function testUpdate() $snippet = $this->snippetFromMethod(Transaction::class, 'update'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -291,13 +313,16 @@ public function testUpdate() $this->assertArrayHasKey('update', $mutations[0]); } - public function testUpdateBatch() { $snippet = $this->snippetFromMethod(Transaction::class, 'updateBatch'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -310,7 +335,11 @@ public function testInsertOrUpdate() $snippet = $this->snippetFromMethod(Transaction::class, 'insertOrUpdate'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -318,15 +347,22 @@ public function testInsertOrUpdate() $this->assertArrayHasKey('insertOrUpdate', $mutations[0]); } - public function testInsertOrUpdateBatch() { - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'insertOrUpdateBatch'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -339,7 +375,11 @@ public function testReplace() $snippet = $this->snippetFromMethod(Transaction::class, 'replace'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -347,13 +387,16 @@ public function testReplace() $this->assertArrayHasKey('replace', $mutations[0]); } - public function testReplaceBatch() { $snippet = $this->snippetFromMethod(Transaction::class, 'replaceBatch'); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -367,7 +410,11 @@ public function testDelete() $snippet->addUse(KeySet::class); $snippet->addLocal('mutationGroup', $this->transaction); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $res = $snippet->invoke(); @@ -377,10 +424,13 @@ public function testDelete() public function testRollback() { - $this->connection->rollback(Argument::any()) - ->shouldBeCalled(); + $this->mockSendRequest(SpannerClient::class, 'rollback', null, null); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'rollback'); $snippet->addLocal('transaction', $this->transaction); @@ -390,13 +440,18 @@ public function testRollback() public function testCommit() { - $this->connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + null, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + ] + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'commit'); $snippet->addLocal('transaction', $this->transaction); @@ -407,14 +462,19 @@ public function testCommit() public function testGetCommitStats() { $expectedCommitStats = new CommitStats(['mutation_count' => 4]); - $this->connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString(), + $this->spannerClient->commit( + null, + [ + 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString(), 'commitStats' => $expectedCommitStats, - ]); + ] + ); - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'getCommitStats'); $snippet->addLocal('transaction', $this->transaction); @@ -437,7 +497,6 @@ public function testIsRetry() $snippet = $this->snippetFromMethod(Transaction::class, 'isRetry'); $snippet->addLocal('transaction', $this->transaction); - $this->transaction->___setProperty('isRetry', true); $res = $snippet->invoke(); $this->assertEquals('This is a retry transaction!', $res->output()); diff --git a/Spanner/tests/Snippet/TransactionalReadMethodsTest.php b/Spanner/tests/Snippet/TransactionalReadMethodsTest.php index 5ad92609999c..9943f3c7a613 100644 --- a/Spanner/tests/Snippet/TransactionalReadMethodsTest.php +++ b/Spanner/tests/Snippet/TransactionalReadMethodsTest.php @@ -17,7 +17,6 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; @@ -29,11 +28,9 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\V1\Gapic\SpannerGapicClient; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -49,9 +46,7 @@ class TransactionalReadMethodsTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; @@ -59,7 +54,8 @@ class TransactionalReadMethodsTest extends SnippetTestCase const TRANSACTION = 'my-transaction'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; - private $connection; + private $spannerClient; + private $serializer; private $session; private $operation; @@ -71,12 +67,14 @@ public function setUp(): void { parent::setUpBeforeClass(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $this->session = $this->prophesize(Session::class); $this->session->info() ->willReturn([ 'databaseName' => 'database' ]); + $this->session->name() + ->willReturn('sessionName'); $this->operation = $this->prophesize(Operation::class); } @@ -97,25 +95,28 @@ public function testExecute($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator([ + $this->spannerClient->executeStreamingSql( + null, + $this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ [ 'name' => 'loginCount', - 'type' => [ - 'code' => Database::TYPE_INT64 - ] + 'type' => ['code' => Database::TYPE_INT64] ] ] ] ], 'values' => [0] - ])); + ]) + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation( + $client, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet->addLocal($localName, $client); @@ -140,37 +141,35 @@ public function testExecuteWithParameterType($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->executeStreamingSql(Argument::that(function ($arg) { - if (!isset($arg['params'])) { - return false; - } - - if (!isset($arg['paramTypes'])) { - return false; - } - - if ($arg['paramTypes']['timestamp']['code'] !== Database::TYPE_TIMESTAMP) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'timestamp', - 'type' => [ - 'code' => Database::TYPE_TIMESTAMP + $this->spannerClient->executeStreamingSql( + function ($args) { + $message = $this->serializer->encodeMessage($args); + $this->assertTrue(isset($message['params'])); + $this->assertTrue(isset($message['paramTypes'])); + $this->assertEquals( + $message['paramTypes']['timestamp']['code'], + Database::TYPE_TIMESTAMP + ); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'timestamp', + 'type' => [ + 'code' => Database::TYPE_TIMESTAMP + ] ] ] ] - ] - ], - 'values' => [null] - ])); + ], + 'values' => [null] + ]) + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); $snippet->addLocal($localName, $client); @@ -195,44 +194,42 @@ public function testExecuteWithEmptyArray($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->executeStreamingSql(Argument::that(function ($arg) { - if (!isset($arg['params'])) { - return false; - } - - if (!isset($arg['paramTypes'])) { - return false; - } - - if ($arg['paramTypes']['emptyArrayOfIntegers']['code'] !== Database::TYPE_ARRAY) { - return false; - } - - if ($arg['paramTypes']['emptyArrayOfIntegers']['arrayElementType']['code'] !== Database::TYPE_INT64) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'numbers', - 'type' => [ - 'code' => Database::TYPE_ARRAY, - 'arrayElementType' => [ - 'code' => Database::TYPE_INT64 + $this->spannerClient->executeStreamingSql( + function ($args) { + $message = $this->serializer->encodeMessage($args); + $this->assertTrue(isset($message['params'])); + $this->assertTrue(isset($message['paramTypes'])); + $this->assertEquals( + $message['paramTypes']['emptyArrayOfIntegers']['code'], + Database::TYPE_ARRAY + ); + $this->assertEquals( + $message['paramTypes']['emptyArrayOfIntegers']['arrayElementType']['code'], + Database::TYPE_INT64 + ); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'numbers', + 'type' => [ + 'code' => Database::TYPE_ARRAY, + 'arrayElementType' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] ] - ] - ], - 'values' => [[]] - ])); + ], + 'values' => [[]] + ]) + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); $snippet->addLocal($localName, $client); @@ -261,12 +258,16 @@ public function testExecuteStruct($localName, $client, $snippet) [ 'name' => 'firstName', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ 'name' => 'lastName', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ] ]; @@ -276,29 +277,31 @@ public function testExecuteStruct($localName, $client, $snippet) 'Testuser' ]; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', 'SELECT @userStruct.firstName, @userStruct.lastName'), - Argument::withEntry('params', [ - 'userStruct' => $values - ]), - Argument::withEntry('paramTypes', [ - 'userStruct' => [ - 'code' => Database::TYPE_STRUCT, - 'structType' => [ + $this->spannerClient->executeStreamingSql( + function ($args) use ($values, $fields) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals($message['sql'], 'SELECT @userStruct.firstName, @userStruct.lastName'); + $this->assertEquals( + $message['params'], + ['userStruct' => $values] + ); + $this->assertEquals( + $message['paramTypes']['userStruct']['structType']['fields'], + $fields + ); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ 'fields' => $fields ] - ] + ], + 'values' => $values ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], - 'values' => $values - ])); + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); $snippet->addLocal($localName, $client); @@ -327,16 +330,23 @@ public function testExecuteStructDuplicateAndUnnamedFields($localName, $client, [ 'name' => 'foo', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ 'name' => 'foo', 'type' => [ - 'code' => Database::TYPE_INT64 + 'code' => Database::TYPE_INT64, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ], [ + 'name' => '', 'type' => [ - 'code' => Database::TYPE_STRING + 'code' => Database::TYPE_STRING, + 'typeAnnotation' => 0, + 'protoTypeFqn' => '' ] ] ]; @@ -347,29 +357,44 @@ public function testExecuteStructDuplicateAndUnnamedFields($localName, $client, 'this field is unnamed' ]; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', 'SELECT * FROM UNNEST(ARRAY(SELECT @structParam))'), - Argument::withEntry('params', [ - 'structParam' => $values - ]), - Argument::withEntry('paramTypes', [ - 'structParam' => [ - 'code' => Database::TYPE_STRUCT, - 'structType' => [ + $this->spannerClient->executeStreamingSql( + function ($args) use ($values, $fields) { + $message = $this->serializer->encodeMessage($args); + $this->assertEquals( + $message['sql'], + 'SELECT * FROM UNNEST(ARRAY(SELECT @structParam))' + ); + $this->assertEquals($message['params'], ['structParam' => $values]); + $this->assertEquals( + $message['paramTypes'], + [ + 'structParam' => [ + 'code' => Database::TYPE_STRUCT, + 'structType' => [ + 'fields' => $fields + ], + 'typeAnnotation' => 0, + 'protoTypeFqn' => '', + ] + ] + ); + return true; + }, + $this->resultGenerator([ + 'metadata' => [ + 'rowType' => [ 'fields' => $fields ] - ] + ], + 'values' => $values ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], - 'values' => $values - ])); + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation( + $client, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet->addLocal($localName, $client); @@ -396,9 +421,9 @@ public function testRead($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator([ + $this->spannerClient->streamingRead( + null, + $this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -412,9 +437,10 @@ public function testRead($localName, $client, $snippet) ] ], 'rows' => [0] - ])); + ]) + ); - $this->refreshOperation($client, $this->connection->reveal()); + $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); $snippet->addLocal($localName, $client); @@ -436,10 +462,9 @@ private function setupDatabase() ->willReturn(null); return \Google\Cloud\Core\Testing\TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() @@ -466,18 +491,18 @@ private function setupSnapshot() $this->session->reveal(), [ 'id' => self::TRANSACTION, - 'readTimestamp' => new Timestamp(new \DateTime) + 'readTimestamp' => new Timestamp(new \DateTime()) ] ], ['operation']); } private function setupBatch() { - $sessData = SpannerGapicClient::parseName(self::SESSION, 'session'); + $sessData = SpannerClient::parseName(self::SESSION, 'session'); $this->session->name()->willReturn(self::SESSION); $this->session->info()->willReturn($sessData + [ 'name' => self::SESSION, - 'databaseName' => SpannerGapicClient::databaseName( + 'databaseName' => SpannerClient::databaseName( self::PROJECT, self::INSTANCE, self::DATABASE @@ -485,7 +510,7 @@ private function setupBatch() ]); return \Google\Cloud\Core\Testing\TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->connection->reveal(), false), + new Operation($this->requestHandler->reveal(), $this->serializer, false), $this->session->reveal(), [ 'id' => self::TRANSACTION, diff --git a/Spanner/tests/StubCreationTrait.php b/Spanner/tests/StubCreationTrait.php deleted file mode 100644 index 5c0d328171f6..000000000000 --- a/Spanner/tests/StubCreationTrait.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests; - -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; - -/** - * Creates stubs with required global expectations. - */ -trait StubCreationTrait -{ - use ProphecyTrait; - - private function getConnStub() - { - $c = $this->prophesize(ConnectionInterface::class); - $c->deleteSession(Argument::any())->willReturn([]); - - return $c; - } -} diff --git a/Spanner/tests/System/AdminTest.php b/Spanner/tests/System/AdminTest.php index 9409f94916c9..98f64f6df584 100644 --- a/Spanner/tests/System/AdminTest.php +++ b/Spanner/tests/System/AdminTest.php @@ -17,12 +17,11 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Exception\FailedPreconditionException; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig\Type; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; @@ -63,7 +62,7 @@ public function testInstance() 'processingUnits' => $processingUnits, ]); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); $op->pollUntilComplete(); $instance = $client->instance(self::INSTANCE_NAME); @@ -95,8 +94,9 @@ public function testDatabase() $dbName = uniqid(self::TESTING_PREFIX); $op = $instance->createDatabase($dbName); - $this->assertInstanceOf(LongRunningOperation::class, $op); - $db = $op->pollUntilComplete(); + $this->assertInstanceOf(OperationResponse::class, $op); + $op->pollUntilComplete(); + $db = $op->getResult(); $this->assertInstanceOf(Database::class, $db); self::$deletionQueue->add(function () use ($db) { @@ -114,7 +114,7 @@ public function testDatabase() $expectedDatabaseDialect = DatabaseDialect::GOOGLE_STANDARD_SQL; // TODO: Remove this, when the emulator supports PGSQL - if ((bool) getenv("SPANNER_EMULATOR_HOST")) { + if ((bool) getenv('SPANNER_EMULATOR_HOST')) { $expectedDatabaseDialect = DatabaseDialect::DATABASE_DIALECT_UNSPECIFIED; } @@ -122,7 +122,7 @@ public function testDatabase() $stmt = "CREATE TABLE Ids (\n" . " id INT64 NOT NULL,\n" . - ") PRIMARY KEY(id)"; + ') PRIMARY KEY(id)'; $op = $db->updateDdl($stmt); $op->pollUntilComplete(); @@ -138,7 +138,7 @@ public function testDatabaseDropProtection() $dbName = uniqid(self::TESTING_PREFIX); $op = $instance->createDatabase($dbName); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); $db = $op->pollUntilComplete(); $this->assertInstanceOf(Database::class, $db); @@ -203,7 +203,7 @@ public function testCreateCustomerManagedInstanceConfiguration() $replicas[array_rand($replicas)]['defaultLeaderLocation'] = true; $op = $customConfiguration->create($baseConfig, $replicas); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); $op->pollUntilComplete(); $this->assertTrue($customConfiguration->exists()); @@ -289,7 +289,7 @@ public function testPgDatabase() 'databaseDialect' => DatabaseDialect::POSTGRESQL ]); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); $db = $op->pollUntilComplete(); $this->assertInstanceOf(Database::class, $db); diff --git a/Spanner/tests/System/BackupTest.php b/Spanner/tests/System/BackupTest.php index 5d8633c6e7e0..3d4ceae147e6 100644 --- a/Spanner/tests/System/BackupTest.php +++ b/Spanner/tests/System/BackupTest.php @@ -17,14 +17,13 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Exception\BadRequestException; use Google\Cloud\Core\Exception\ConflictException; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupEncryptionConfig; -use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Database\V1\EncryptionConfig; use Google\Cloud\Spanner\Admin\Database\V1\EncryptionInfo\Type; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Date; @@ -77,7 +76,7 @@ public static function setUpTestFixtures(): void }); $db1->updateDdl( - 'CREATE TABLE '. self::TEST_TABLE_NAME .' ( + 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( id INT64 NOT NULL, name STRING(MAX) NOT NULL, birthday DATE NOT NULL @@ -95,7 +94,7 @@ public static function setUpTestFixtures(): void }); $db2->updateDdl( - 'CREATE TABLE '. self::TEST_TABLE_NAME .' ( + 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( id INT64 NOT NULL, name STRING(MAX) NOT NULL, birthday DATE NOT NULL @@ -106,8 +105,8 @@ public static function setUpTestFixtures(): void self::insertData(10, self::$dbName2); self::$backupId1 = uniqid(self::BACKUP_PREFIX); - self::$backupId2 = uniqid("users-"); - self::$copyBackupId = uniqid("copy-"); + self::$backupId2 = uniqid('users-'); + self::$copyBackupId = uniqid('copy-'); self::$hasSetUp = true; } @@ -124,7 +123,6 @@ public function testListAllInstances() public function testCreateBackup() { $expireTime = new \DateTime('+7 hours'); - $versionTime = new \DateTime('-5 seconds'); $encryptionConfig = [ 'encryptionType' => CreateBackupEncryptionConfig\EncryptionType::GOOGLE_DEFAULT_ENCRYPTION, ]; @@ -134,7 +132,6 @@ public function testCreateBackup() self::$createTime1 = gmdate('"Y-m-d\TH:i:s\Z"'); $op = $backup->create(self::$dbName1, $expireTime, [ - 'versionTime' => $versionTime, 'encryptionConfig' => $encryptionConfig, ]); self::$backupOperationName = $op->name(); @@ -200,7 +197,7 @@ public function testCreateBackupInvalidArgument() $e = null; try { $backup->create(self::$dbName1, $expireTime, [ - 'versionTime' => "invalidType", + 'versionTime' => 'invalidType', ]); } catch (\InvalidArgumentException $e) { } @@ -220,6 +217,9 @@ public function testCreateBackupInvalidArgument() $this->assertFalse($backup->exists()); } + /** + * @depends testCreateBackup + */ public function testCancelBackupOperation() { $expireTime = new \DateTime('+7 hours'); @@ -335,6 +335,9 @@ public function testUpdateExpirationTimeFailed() $this->assertEquals($currentExpireTime, $backup->info()['expireTime']); } + /** + * @depends testCreateBackup + */ public function testListAllBackups() { $allBackups = iterator_to_array(self::$instance->backups(), false); @@ -347,6 +350,9 @@ public function testListAllBackups() $this->assertContainsOnlyInstancesOf(Backup::class, $allBackups); } + /** + * @depends testCreateBackup + */ public function testListAllBackupsContainsName() { $backups = iterator_to_array(self::$instance->backups(['filter' => 'name:' . self::$backupId1])); @@ -354,9 +360,12 @@ public function testListAllBackupsContainsName() $this->assertEquals(self::$backupId1, DatabaseAdminClient::parseName($backups[0]->info()['name'])['backup']); } + /** + * @depends testCreateBackup + */ public function testListAllBackupsReady() { - $backups = iterator_to_array(self::$instance->backups(['filter'=>'state:READY'])); + $backups = iterator_to_array(self::$instance->backups(['filter' => 'state:READY'])); $backupNames = []; foreach ($backups as $b) { @@ -366,6 +375,9 @@ public function testListAllBackupsReady() $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); } + /** + * @depends testCreateBackup + */ public function testListAllBackupsOfDatabase() { $database = self::$instance->database(self::$dbName1); @@ -378,26 +390,31 @@ public function testListAllBackupsOfDatabase() } } + /** + * @depends testCreateBackup + */ public function testListAllBackupsCreatedAfterTimestamp() { - $filter = sprintf("create_time >= %s", self::$createTime2); + $filter = sprintf('create_time >= %s', self::$createTime1); - $backups = iterator_to_array(self::$instance->backups(['filter'=>$filter])); + $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); $backupNames = []; foreach ($backups as $b) { $backupNames[] = $b->name(); } $this->assertTrue(count($backupNames) > 0); - $this->assertFalse(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); - $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId2), $backupNames)); + $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId1), $backupNames)); } + /** + * @depends testCreateBackup + */ public function testListAllBackupsExpireBeforeTimestamp() { - $filter = "expire_time < " . gmdate('"Y-m-d\TH:i:s\Z"', strtotime('+9 hours')); + $filter = 'expire_time < ' . gmdate('"Y-m-d\TH:i:s\Z"', strtotime('+9 hours')); - $backups = iterator_to_array(self::$instance->backups(['filter'=>$filter])); + $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); $backupNames = []; foreach ($backups as $b) { @@ -415,7 +432,7 @@ public function testListAllBackupsWithSizeGreaterThanSomeBytes() { $backup = self::$instance->backup(self::$backupId1); $size = $backup->info()['sizeBytes']; - $filter = "size_bytes > " . $size; + $filter = 'size_bytes > ' . $size; $backups = iterator_to_array(self::$instance->backups(['filter' => $filter])); @@ -429,6 +446,9 @@ public function testListAllBackupsWithSizeGreaterThanSomeBytes() $this->assertTrue(in_array(self::fullyQualifiedBackupName(self::$backupId2), $backupNames)); } + /** + * @depends testCancelBackupOperation + */ public function testPagination() { $backupsfirstPage = self::$instance->backups(['pageSize' => 1]); @@ -445,6 +465,9 @@ public function testPagination() $this->assertEquals(2, count($backupsPageSizeTwo)); } + /** + * @depends testRestoreToNewDatabase + */ public function testListAllBackupOperations() { $backupOps = iterator_to_array($this::$instance->backupOperations()); @@ -454,7 +477,7 @@ public function testListAllBackupOperations() }, $backupOps); $this->assertTrue(count($backupOps) > 0); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $backupOps); + $this->assertContainsOnlyInstancesOf(OperationResponse::class, $backupOps); $this->assertTrue(in_array(self::$backupOperationName, $backupOpsNames)); } @@ -477,7 +500,7 @@ public function testDeleteBackup() public function testDeleteNonExistantBackup() { - $backup = self::$instance->backup("does_not_exis"); + $backup = self::$instance->backup('does_not_exis'); $this->assertFalse($backup->exists()); @@ -557,6 +580,9 @@ public function testRestoreToNewDatabase() $this->assertArrayHasKey('startTime', $metadata['progress']); } + /** + * @depends testRestoreToNewDatabase + */ public function testRestoreAppearsInListDatabaseOperations() { $databaseOps = iterator_to_array($this::$instance->databaseOperations()); @@ -565,10 +591,13 @@ public function testRestoreAppearsInListDatabaseOperations() }, $databaseOps); $this->assertTrue(count($databaseOps) > 0); - $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $databaseOps); + $this->assertContainsOnlyInstancesOf(OperationResponse::class, $databaseOps); $this->assertTrue(in_array(self::$restoreOperationName, $databaseOpsNames)); } + /** + * @depends testCreateBackup + */ public function testRestoreBackupToAnExistingDatabase() { $existingDb = self::$instance->database(self::$dbName2); @@ -603,7 +632,7 @@ private static function generateRows($number) { $rows = []; - for ($id=1; $id <= $number; $id++) { + for ($id = 1; $id <= $number; $id++) { $rows[] = self::generateRow($id, uniqid(self::TESTING_PREFIX), new Date(new \DateTime())); } return $rows; diff --git a/Spanner/tests/System/BatchTest.php b/Spanner/tests/System/BatchTest.php index 2d90a07e6c08..759518d9cbcb 100644 --- a/Spanner/tests/System/BatchTest.php +++ b/Spanner/tests/System/BatchTest.php @@ -17,12 +17,12 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Cloud\Core\Exception\ServiceException; +use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Core\Exception\ServiceException; /** * @group spanner @@ -84,7 +84,7 @@ public static function setUpTestFixtures(): void private static function seedTable() { - $decades = [1950,1960,1970,1980,1990,2000]; + $decades = [1950, 1960, 1970, 1980, 1990, 2000]; for ($i = 0; $i < 250; $i++) { self::$database->insert(self::$tableName, [ 'id' => self::randId(), @@ -121,7 +121,6 @@ public function testBatch() $partitions = $snapshot->partitionQuery($query, ['parameters' => $parameters]); $this->assertEquals(count($resultSet), $this->executePartitions($batch, $snapshot, $partitions)); - // ($table, KeySet $keySet, array $columns, array $options = []) $keySet = new KeySet([ 'ranges' => [ new KeyRange([ @@ -182,60 +181,6 @@ public function testBatchWithDbRole($dbRole, $expected) $snapshot->close(); } - public function testBatchWithDataBoostEnabled() - { - // Emulator does not support dataBoostEnabled - $this->skipEmulatorTests(); - - $query = 'SELECT - id, - decade - FROM ' . self::$tableName . ' - WHERE - decade > @earlyBound - AND - decade < @lateBound'; - - $parameters = [ - 'earlyBound' => 1960, - 'lateBound' => 1980 - ]; - - $resultSet = iterator_to_array(self::$database->execute($query, ['parameters' => $parameters])); - - $batch = self::$client->batch(self::INSTANCE_NAME, self::$dbName); - $string = $batch->snapshot()->serialize(); - - $snapshot = $batch->snapshotFromString($string); - - $partitions = $snapshot->partitionQuery($query, [ - 'parameters' => $parameters, - 'dataBoostEnabled' => true - ]); - $this->assertEquals(count($resultSet), $this->executePartitions($batch, $snapshot, $partitions)); - - $keySet = new KeySet([ - 'ranges' => [ - new KeyRange([ - 'start' => $parameters['earlyBound'], - 'startType' => KeyRange::TYPE_OPEN, - 'end' => $parameters['lateBound'], - 'endType' => KeyRange::TYPE_OPEN - ]) - ] - ]); - - $partitions = $snapshot->partitionRead( - self::$tableName, - $keySet, - ['id', 'decade'], - ['dataBoostEnabled' => true] - ); - $this->assertEquals(count($resultSet), $this->executePartitions($batch, $snapshot, $partitions)); - - $snapshot->close(); - } - private function executePartitions(BatchClient $client, BatchSnapshot $snapshot, array $partitions) { $partitionResultSet = []; diff --git a/Spanner/tests/System/BatchWriteTest.php b/Spanner/tests/System/BatchWriteTest.php index cbcf38031985..d4ce3869f8f8 100644 --- a/Spanner/tests/System/BatchWriteTest.php +++ b/Spanner/tests/System/BatchWriteTest.php @@ -53,16 +53,16 @@ public function testBatchWrite() $mutationGroups = []; $mutationGroups[] = self::$database->mutationGroup() ->insertOrUpdate( - "Singers", + 'Singers', ['SingerId' => 16, 'FirstName' => 'Scarlet', 'LastName' => 'Terry'] ); $mutationGroups[] = self::$database->mutationGroup() ->insertOrUpdate( - "Singers", + 'Singers', ['SingerId' => 17, 'FirstName' => 'Marc', 'LastName' => 'Kristen'] )->insertOrUpdate( - "Albums", + 'Albums', ['AlbumId' => 1, 'SingerId' => 17, 'AlbumTitle' => 'Total Junk'] ); diff --git a/Spanner/tests/System/GeneratedAdminEmulatorTest.php b/Spanner/tests/System/GeneratedAdminEmulatorTest.php index b31d5f409ac0..cef39e7faef7 100644 --- a/Spanner/tests/System/GeneratedAdminEmulatorTest.php +++ b/Spanner/tests/System/GeneratedAdminEmulatorTest.php @@ -42,7 +42,7 @@ public static function setUpTestFixtures(): void public function testAdminClientEmulatorSupport() { - if (!getenv("SPANNER_EMULATOR_HOST")) { + if (!getenv('SPANNER_EMULATOR_HOST')) { self::markTestSkipped('This test is required to run only in the emulator.'); } diff --git a/Spanner/tests/System/LargeReadTest.php b/Spanner/tests/System/LargeReadTest.php index e491aca6e8f4..4fe2217c22ac 100644 --- a/Spanner/tests/System/LargeReadTest.php +++ b/Spanner/tests/System/LargeReadTest.php @@ -31,7 +31,7 @@ class LargeReadTest extends SpannerTestCase //@codingStandardsIgnoreStart private static $data = [ - 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ]; //@codingStandardsIgnoreEnd @@ -80,7 +80,7 @@ private static function seedTable() 'bytesArrayColumn' => self::randomArrayOfBytes($str), ]; - for ($i=0; $i < 10; $i++) { + for ($i = 0; $i < 10; $i++) { self::$database->insert(self::$tableName, self::$row + ['id' => self::randId()], [ 'timeoutMillis' => 50000 ]); @@ -143,7 +143,7 @@ private static function randomBytes(&$str) private static function randomArrayOfStrings() { $res = []; - for ($i=0; $i <= rand(1, 4); $i++) { + for ($i = 0; $i <= rand(1, 4); $i++) { $res[] = self::randomString(); } @@ -153,7 +153,7 @@ private static function randomArrayOfStrings() private static function randomArrayOfBytes(&$str) { $res = []; - for ($i=0; $i <= rand(1, 4); $i++) { + for ($i = 0; $i <= rand(1, 4); $i++) { $res[] = self::randomBytes($str); } diff --git a/Spanner/tests/System/OperationsTest.php b/Spanner/tests/System/OperationsTest.php index 50e88cb6fb80..77b857206403 100644 --- a/Spanner/tests/System/OperationsTest.php +++ b/Spanner/tests/System/OperationsTest.php @@ -17,9 +17,9 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Spanner\Date; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Core\Exception\ServiceException; /** * @group spanner @@ -147,7 +147,7 @@ public function testEmptyRead() $keySet = self::$client->keySet(['keys' => [99999]]); - $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id','name']); + $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id', 'name']); $this->assertEmpty(iterator_to_array($res->rows())); } @@ -157,7 +157,7 @@ public function testEmptyReadOnIndex() $keySet = self::$client->keySet(['keys' => [99999]]); - $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id','name'], [ + $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id', 'name'], [ 'index' => self::TEST_INDEX_NAME ]); @@ -185,7 +185,7 @@ public function testReadNonExistentSingleKey() 'keys' => [99999] ]); - $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id','name']); + $res = $db->read(self::TEST_TABLE_NAME, $keySet, ['id', 'name']); $this->assertEmpty(iterator_to_array($res->rows())); } diff --git a/Spanner/tests/System/PartitionedDmlTest.php b/Spanner/tests/System/PartitionedDmlTest.php index 50f4ed7ded02..a2973b968ef9 100644 --- a/Spanner/tests/System/PartitionedDmlTest.php +++ b/Spanner/tests/System/PartitionedDmlTest.php @@ -17,8 +17,6 @@ namespace Google\Cloud\Spanner\Tests\System; -use Google\Cloud\Spanner\Tests\System\SpannerTestCase; - /** * @group spanner * @group spanner-pdml diff --git a/Spanner/tests/System/PgBatchTest.php b/Spanner/tests/System/PgBatchTest.php index 131ba743a084..de4dca0b0267 100644 --- a/Spanner/tests/System/PgBatchTest.php +++ b/Spanner/tests/System/PgBatchTest.php @@ -17,10 +17,10 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Cloud\Core\Exception\ServiceException; +use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; -use Google\Cloud\Core\Exception\ServiceException; /** * @group spanner @@ -140,7 +140,7 @@ private function executePartitions(BatchClient $client, BatchSnapshot $snapshot, private static function seedTable() { - $decades = [1950,1960,1970,1980,1990,2000]; + $decades = [1950, 1960, 1970, 1980, 1990, 2000]; for ($i = 0; $i < 250; $i++) { self::$database->insert(self::$tableName, [ 'id' => self::randId(), diff --git a/Spanner/tests/System/PgBatchWriteTest.php b/Spanner/tests/System/PgBatchWriteTest.php index d7563bbe5ccf..dc09cfdc8834 100644 --- a/Spanner/tests/System/PgBatchWriteTest.php +++ b/Spanner/tests/System/PgBatchWriteTest.php @@ -55,16 +55,16 @@ public function testBatchWrite() $mutationGroups = []; $mutationGroups[] = self::$database->mutationGroup() ->insertOrUpdate( - "Singers", + 'Singers', ['SingerId' => 16, 'FirstName' => 'Scarlet', 'LastName' => 'Terry'] ); $mutationGroups[] = self::$database->mutationGroup() ->insertOrUpdate( - "Singers", + 'Singers', ['SingerId' => 17, 'FirstName' => 'Marc', 'LastName' => 'Kristen'] )->insertOrUpdate( - "Albums", + 'Albums', ['AlbumId' => 1, 'SingerId' => 17, 'AlbumTitle' => 'Total Junk'] ); diff --git a/Spanner/tests/System/PgPartitionedDmlTest.php b/Spanner/tests/System/PgPartitionedDmlTest.php index 6906090f51bd..e9fc42e0a591 100644 --- a/Spanner/tests/System/PgPartitionedDmlTest.php +++ b/Spanner/tests/System/PgPartitionedDmlTest.php @@ -17,8 +17,6 @@ namespace Google\Cloud\Spanner\Tests\System; -use Google\Cloud\Spanner\Tests\System\SpannerPgTestCase; - /** * @group spanner * @group spanner-pdml diff --git a/Spanner/tests/System/PgQueryTest.php b/Spanner/tests/System/PgQueryTest.php index b45766cfa491..38ddefea403a 100644 --- a/Spanner/tests/System/PgQueryTest.php +++ b/Spanner/tests/System/PgQueryTest.php @@ -23,8 +23,8 @@ use Google\Cloud\Spanner\Bytes; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; -use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\PgJsonb; +use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; use Google\Cloud\Spanner\V1\RequestOptions\Priority; @@ -63,7 +63,7 @@ public static function setUpTestFixtures(): void )' )->pollUntilComplete(); - self::$timestampVal = new Timestamp(new \DateTime); + self::$timestampVal = new Timestamp(new \DateTime()); self::$database->insertOrUpdateBatch(self::TABLE_NAME, [ [ @@ -312,7 +312,7 @@ public function testBindPgNumericParameter() $row = $res->rows()->current(); $this->assertInstanceOf(PgNumeric::class, $row['age']); $this->assertEquals($str, $val->formatAsString()); - $this->assertEquals($str, (string)$val->get()); + $this->assertEquals($str, (string) $val->get()); } public function testBindPgNumericParameterNull() @@ -358,7 +358,7 @@ public function testBindBytesParameter() $row = $res->rows()->current(); $this->assertInstanceOf(Bytes::class, $row['bytes_col']); $this->assertEquals($str, base64_decode($bytes->formatAsString())); - $this->assertEquals($str, (string)$bytes->get()); + $this->assertEquals($str, (string) $bytes->get()); } public function testBindBytesParameterNull() @@ -437,7 +437,7 @@ public function testBindTimestampParameterNull() public function testBindDateParameter() { - $res = self::$database->execute("SELECT * FROM " . self::TABLE_NAME . " WHERE dt BETWEEN $1 AND $2", [ + $res = self::$database->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE dt BETWEEN $1 AND $2', [ 'parameters' => [ 'p1' => new Date(new \DateTime('2020-01-01')), 'p2' => new Date(new \DateTime('2021-01-01')) @@ -512,7 +512,7 @@ public function testBindPgJsonbParameter() $row = $res->rows()->current(); $this->assertInstanceOf(PgJsonb::class, $row['data']); $this->assertEquals($str, $val->formatAsString()); - $this->assertEquals($str, (string)$val->get()); + $this->assertEquals($str, (string) $val->get()); } public function testBindJsonbParameterNull() @@ -579,16 +579,16 @@ public function arrayTypesProvider() { return [ // boolean - [[true,true,false]], + [[true, true, false]], // int64 - [[5,4,3,2,1]], + [[5, 4, 3, 2, 1]], // float64 [[3.14, 4.13, 1.43]], // string - [['hello','world','google','cloud']], + [['hello', 'world', 'google', 'cloud']], // bytes [ @@ -661,7 +661,7 @@ function (array $res) { [ new PgJsonb('{}'), new PgJsonb('{"a": "b"}'), - new PgJsonb(["a" => "b"]) + new PgJsonb(['a' => 'b']) ], ['{}', '{"a": "b"}', '{"a": "b"}'], PgJsonb::class, @@ -674,7 +674,7 @@ function (array $res) { } ], // pg_oid - [[5,4,3,2,1]], + [[5, 4, 3, 2, 1]], ]; } diff --git a/Spanner/tests/System/PgReadTest.php b/Spanner/tests/System/PgReadTest.php index 30ce4c48dd14..1737c95710de 100644 --- a/Spanner/tests/System/PgReadTest.php +++ b/Spanner/tests/System/PgReadTest.php @@ -41,8 +41,8 @@ public static function setUpTestFixtures(): void { parent::setUpTestFixtures(); - self::$readTableName = "read_table"; - self::$rangeTableName = "range_table"; + self::$readTableName = 'read_table'; + self::$rangeTableName = 'range_table'; $create = 'CREATE TABLE %s ( id bigint NOT NULL, @@ -312,7 +312,7 @@ public function testReadWithLimit() }; $limitCount = count(iterator_to_array($res(10))); - $unlimitCount = count(iterator_to_array($res(null))); + $unlimitCount = count(iterator_to_array($res(0))); $this->assertEquals(10, $limitCount); $this->assertNotEquals($limitCount, $unlimitCount); @@ -331,7 +331,7 @@ public function testReadOverIndexWithLimit() }; $limitCount = count(iterator_to_array($res(10))); - $unlimitCount = count(iterator_to_array($res(null))); + $unlimitCount = count(iterator_to_array($res(0))); $this->assertEquals(10, $limitCount); $this->assertNotEquals($limitCount, $unlimitCount); diff --git a/Spanner/tests/System/PgTransactionTest.php b/Spanner/tests/System/PgTransactionTest.php index f6f807c33173..37fdbbb58430 100644 --- a/Spanner/tests/System/PgTransactionTest.php +++ b/Spanner/tests/System/PgTransactionTest.php @@ -48,7 +48,7 @@ public static function setUpTestFixtures(): void } parent::setUpTestFixtures(); - self::$tableName = "transactions_test"; + self::$tableName = 'transactions_test'; self::$database->updateDdlBatch([ 'CREATE TABLE ' . self::$tableName . ' ( diff --git a/Spanner/tests/System/PgWriteTest.php b/Spanner/tests/System/PgWriteTest.php index 037efa24ead8..27adf4902333 100644 --- a/Spanner/tests/System/PgWriteTest.php +++ b/Spanner/tests/System/PgWriteTest.php @@ -25,9 +25,9 @@ use Google\Cloud\Spanner\CommitTimestamp; use Google\Cloud\Spanner\Date; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\PgJsonb; +use Google\Cloud\Spanner\PgNumeric; +use Google\Cloud\Spanner\Timestamp; use Google\Rpc\Code; /** @@ -96,7 +96,7 @@ public function fieldValueProvider() [$this->randId(), 'datefield', new Date(new \DateTime('1981-01-20'))], [$this->randId(), 'intfield', 787878787], [$this->randId(), 'stringfield', 'foo bar'], - [$this->randId(), 'timestampfield', new Timestamp(new \DateTime)], + [$this->randId(), 'timestampfield', new Timestamp(new \DateTime())], [$this->randId(), 'pgnumericfield', new PgNumeric('0.123456789')], [$this->randId(), 'pgjsonbfield', new PgJsonb('{}')], [$this->randId(), 'pgjsonbfield', new PgJsonb('{"a": 1.1, "b": "def"}')], @@ -240,9 +240,9 @@ public function arrayFieldValueProvider() { return [ [$this->randId(), 'arrayfield', []], - [$this->randId(), 'arrayfield', [1,2,null,4,5]], + [$this->randId(), 'arrayfield', [1, 2, null, 4, 5]], [$this->randId(), 'arrayfield', null], - [$this->randId(), 'arrayboolfield', [true,false]], + [$this->randId(), 'arrayboolfield', [true, false]], [$this->randId(), 'arrayboolfield', []], [$this->randId(), 'arrayboolfield', [true, false, null, false]], [$this->randId(), 'arrayboolfield', null], @@ -254,9 +254,9 @@ public function arrayFieldValueProvider() [$this->randId(), 'arrayfloat4field', []], [$this->randId(), 'arrayfloat4field', [1.1, null, 1.3]], [$this->randId(), 'arrayfloat4field', null], - [$this->randId(), 'arraystringfield', ['foo','bar','baz']], + [$this->randId(), 'arraystringfield', ['foo', 'bar', 'baz']], [$this->randId(), 'arraystringfield', []], - [$this->randId(), 'arraystringfield', ['foo',null,'baz']], + [$this->randId(), 'arraystringfield', ['foo', null, 'baz']], [$this->randId(), 'arraystringfield', null], [$this->randId(), 'arraybytesfield', []], [$this->randId(), 'arraybytesfield', null], @@ -308,13 +308,14 @@ public function testWriteAndReadBackArrayValue($id, $field, $value) public function arrayFieldComplexValueProvider() { + $timestamp = new Timestamp(new \DateTime()); return [ - [$this->randId(), 'arraybytesfield', [new Bytes('foo'),null,new Bytes('baz')]], - [$this->randId(), 'arraytimestampfield', [new Timestamp(new \DateTime),null,new Timestamp(new \DateTime)]], - [$this->randId(), 'arraydatefield', [new Date(new \DateTime),null,new Date(new \DateTime)]], - [$this->randId(), 'arraypgnumericfield', [new PgNumeric("0.12345"),null,new PgNumeric("12345")]], - [$this->randId(), 'arraypgjsonbfield', [new PgJsonb('{"a":1.1,"b":"hello"}'),null, - new PgJsonb(["a" => 1, "b" => null]),new PgJsonb('{}'),new PgJsonb([])]], + [$this->randId(), 'arraybytesfield', [new Bytes('foo'), null, new Bytes('baz')]], + [$this->randId(), 'arraytimestampfield', [$timestamp, null, $timestamp]], + [$this->randId(), 'arraydatefield', [new Date(new \DateTime()), null, new Date(new \DateTime())]], + [$this->randId(), 'arraypgnumericfield', [new PgNumeric('0.12345'), null, new PgNumeric('12345')]], + [$this->randId(), 'arraypgjsonbfield', [new PgJsonb('{"a":1.1,"b":"hello"}'), null, + new PgJsonb(['a' => 1, 'b' => null]), new PgJsonb('{}'), new PgJsonb([])]], ]; } @@ -440,11 +441,11 @@ public function testWriteAndReadBackRandomNumeric($id, $numeric) public function randomNumericProvider() { return [ - [$this->randId(), new PgNumeric((string)rand(100, 9999))], - [$this->randId(), new PgNumeric((string)rand(100, 9999))], - [$this->randId(), new PgNumeric((string)rand(100, 9999))], - [$this->randId(), new PgNumeric((string)rand(100, 9999))], - [$this->randId(), new PgNumeric((string)rand(100, 9999))], + [$this->randId(), new PgNumeric((string) rand(100, 9999))], + [$this->randId(), new PgNumeric((string) rand(100, 9999))], + [$this->randId(), new PgNumeric((string) rand(100, 9999))], + [$this->randId(), new PgNumeric((string) rand(100, 9999))], + [$this->randId(), new PgNumeric((string) rand(100, 9999))], ]; } @@ -456,7 +457,7 @@ public function testCommitTimestamp() $id = $this->randId(); $ts = self::$database->insert(self::COMMIT_TIMESTAMP_TABLE_NAME, [ 'id' => $id, - 'committimestamp' => new CommitTimestamp + 'committimestamp' => new CommitTimestamp() ]); $res = self::$database->execute('SELECT * FROM ' . self::COMMIT_TIMESTAMP_TABLE_NAME . ' WHERE id = $1', [ @@ -581,7 +582,7 @@ public function testTimestampPrecisionLocale($timestamp) public function timestamps() { - $today = new \DateTime; + $today = new \DateTime(); $str = $today->format('Y-m-d\TH:i:s'); $todayLowMs = \DateTime::createFromFormat('U.u', time() . '.012345'); @@ -609,17 +610,17 @@ public function testExecuteUpdate() $db = self::$database; $db->runTransaction(function ($t) use ($id, $randStr) { - $count = $t->executeUpdate( - 'INSERT INTO ' . self::TABLE_NAME . ' (id, stringfield) VALUES ($1, $2)', - [ - 'parameters' => [ - 'p1' => $id, - 'p2' => $randStr - ] + $count = $t->executeUpdate( + 'INSERT INTO ' . self::TABLE_NAME . ' (id, stringfield) VALUES ($1, $2)', + [ + 'parameters' => [ + 'p1' => $id, + 'p2' => $randStr ] - ); + ] + ); - $this->assertEquals(1, $count); + $this->assertEquals(1, $count); $row = $t->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = $1', [ 'parameters' => [ diff --git a/Spanner/tests/System/QueryTest.php b/Spanner/tests/System/QueryTest.php index 3e6f5a68c978..baaf5c80ce1f 100644 --- a/Spanner/tests/System/QueryTest.php +++ b/Spanner/tests/System/QueryTest.php @@ -83,7 +83,7 @@ public function testQueryReturnsArrayStruct() $res = $db->execute('SELECT ARRAY(SELECT STRUCT(1, 2))'); $row = $res->rows()->current(); - $this->assertEquals($row[0][0], [1,2]); + $this->assertEquals($row[0][0], [1, 2]); } /** @@ -250,7 +250,7 @@ public function testBindNumericParameter() $row = $res->rows()->current(); $this->assertInstanceOf(Numeric::class, $row['foo']); $this->assertEquals($str, $numeric->formatAsString()); - $this->assertEquals($str, (string)$numeric->get()); + $this->assertEquals($str, (string) $numeric->get()); } public function testBindNumericParameterNull() @@ -289,7 +289,7 @@ public function testBindBytesParameter() $row = $res->rows()->current(); $this->assertInstanceOf(Bytes::class, $row['foo']); $this->assertEquals($str, base64_decode($bytes->formatAsString())); - $this->assertEquals($str, (string)$bytes->get()); + $this->assertEquals($str, (string) $bytes->get()); } /** @@ -319,7 +319,7 @@ public function testBindDateParameter() { $db = self::$database; - $ts = new Date(new \DateTimeImmutable); + $ts = new Date(new \DateTimeImmutable()); $res = $db->execute('SELECT @param as foo', [ 'parameters' => [ @@ -609,16 +609,16 @@ public function arrayTypes() { return [ // boolean (covers 37) - [[true,true,false]], + [[true, true, false]], // int64 (covers 40) - [[5,4,3,2,1]], + [[5, 4, 3, 2, 1]], // float64 (covers 43) [[3.14, 4.13, 1.43]], // string (covers 46) - [['hello','world','google','cloud']], + [['hello', 'world', 'google', 'cloud']], // bytes (covers 49) [ @@ -750,7 +750,7 @@ public function testBindStructParameter() 'p4' => 10 ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('userf', Database::TYPE_STRING) ->add('threadf', Database::TYPE_INT64) ] @@ -774,7 +774,7 @@ public function testBindNullStructParameter() 'structParam' => null ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('userf', Database::TYPE_STRING) ->add('threadf', Database::TYPE_INT64) ] @@ -800,10 +800,10 @@ public function testBindNestedStructParameter() ] ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add( 'structf', - (new StructType)->add('nestedf', Database::TYPE_STRING) + (new StructType())->add('nestedf', Database::TYPE_STRING) ) ] ]); @@ -824,10 +824,10 @@ public function testBindNullNestedStructParameter() 'structParam' => null ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add( 'structf', - (new StructType)->add('nestedf', Database::TYPE_STRING) + (new StructType())->add('nestedf', Database::TYPE_STRING) ) ] ]); @@ -847,7 +847,7 @@ public function testBindEmptyStructParameter() 'structParam' => [] ], 'types' => [ - 'structParam' => new StructType + 'structParam' => new StructType() ] ]); @@ -867,7 +867,7 @@ public function testBindStructNoFieldsParameter() 'structParam' => null ], 'types' => [ - 'structParam' => new StructType + 'structParam' => new StructType() ] ]); @@ -889,7 +889,7 @@ public function testBindStructParameterNullFields() ] ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('f1', Database::TYPE_INT64) ] ]); @@ -912,7 +912,7 @@ public function testBindStructParameterEqualityCheck() ] ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('threadf', Database::TYPE_INT64) ->add('userf', Database::TYPE_STRING) ] @@ -936,7 +936,7 @@ public function testBindStructParameterNullCheck() ] ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('userf', Database::TYPE_STRING) ->add('threadf', Database::TYPE_INT64) ] @@ -964,9 +964,9 @@ public function testBindArrayOfStructsParameter() ], ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('arraysf', new ArrayType( - (new StructType)->add('threadid', Database::TYPE_INT64) + (new StructType())->add('threadid', Database::TYPE_INT64) )) ] ]); @@ -989,9 +989,9 @@ public function testBindArrayOfStructsNullParameter() ], ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('arraysf', new ArrayType( - (new StructType)->add('threadid', Database::TYPE_INT64) + (new StructType())->add('threadid', Database::TYPE_INT64) )) ] ]); @@ -1011,9 +1011,9 @@ public function testBindNullArrayOfStructsParameter() 'structParam' => null ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('arraysf', new ArrayType( - (new StructType)->add('threadid', Database::TYPE_INT64) + (new StructType())->add('threadid', Database::TYPE_INT64) )) ] ]); @@ -1030,13 +1030,13 @@ public function testBindArrayOfStructsDuplicateFieldName() $db = self::$database; $res = $db->execute('SELECT * FROM UNNEST(ARRAY(SELECT @structParam))', [ 'parameters' => [ - 'structParam' => (new StructValue) + 'structParam' => (new StructValue()) ->add('hello', 'world') ->add('foo', 'bar') ->add('foo', 2) ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('hello', Database::TYPE_STRING) ->add('foo', Database::TYPE_STRING) ->add('foo', Database::TYPE_INT64) @@ -1067,15 +1067,15 @@ public function testBindStructWithMixedUnnamedParameters() $db = self::$database; $res = $db->execute('SELECT * FROM UNNEST(ARRAY(SELECT @structParam))', [ 'parameters' => [ - 'structParam' => (new StructValue) + 'structParam' => (new StructValue()) ->addUnnamed(1) ->add('f1', 2) ->addUnnamed([ - 'a','b','c' + 'a', 'b', 'c' ]) ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->addUnnamed(Database::TYPE_INT64) ->add('f1', Database::TYPE_INT64) ->addUnnamed(new ArrayType(Database::TYPE_STRING)) @@ -1085,7 +1085,7 @@ public function testBindStructWithMixedUnnamedParameters() $this->assertEquals(1, $res[0]); $this->assertEquals(2, $res['f1']); $this->assertEquals([ - 'a','b','c' + 'a', 'b', 'c' ], $res[2]); } @@ -1097,16 +1097,16 @@ public function testBindStructWithAllUnnamedParameters() $db = self::$database; $res = $db->execute('SELECT * FROM UNNEST(ARRAY(SELECT @structParam))', [ 'parameters' => [ - 'structParam' => (new StructValue) + 'structParam' => (new StructValue()) ->addUnnamed(1) ->addUnnamed('field') ->addUnnamed([ - 'a','b','c' + 'a', 'b', 'c' ]) ->addUnnamed(false) ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->addUnnamed(Database::TYPE_INT64) ->addUnnamed(Database::TYPE_STRING) ->addUnnamed(new ArrayType(Database::TYPE_STRING)) @@ -1117,7 +1117,7 @@ public function testBindStructWithAllUnnamedParameters() $this->assertEquals(1, $res[0]); $this->assertEquals('field', $res[1]); $this->assertEquals([ - 'a','b','c' + 'a', 'b', 'c' ], $res[2]); $this->assertFalse($res[3]); } @@ -1136,7 +1136,7 @@ public function testBindStructInferredParameterTypes() 'structParam' => $values ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('str', Database::TYPE_STRING) ] ])->rows()->current(); @@ -1155,14 +1155,14 @@ public function testBindStructInferredParameterTypesWithUnnamed() $db = self::$database; $res = $db->execute('SELECT * FROM UNNEST(ARRAY(SELECT @structParam))', [ 'parameters' => [ - 'structParam' => (new StructValue) + 'structParam' => (new StructValue()) ->add('arr', ['a', 'b']) ->addUnnamed('hello') ->addUnnamed(10) ->add('str', 'world') ], 'types' => [ - 'structParam' => (new StructType) + 'structParam' => (new StructType()) ->add('str', Database::TYPE_STRING) ] ])->rows(Result::RETURN_NAME_VALUE_PAIR)->current(); diff --git a/Spanner/tests/System/ReadTest.php b/Spanner/tests/System/ReadTest.php index a6ebcc47a554..95609aac31f9 100644 --- a/Spanner/tests/System/ReadTest.php +++ b/Spanner/tests/System/ReadTest.php @@ -349,7 +349,7 @@ public function testReadWithLimit() }; $limitCount = count(iterator_to_array($res(10))); - $unlimitCount = count(iterator_to_array($res(null))); + $unlimitCount = count(iterator_to_array($res(0))); $this->assertEquals(10, $limitCount); $this->assertNotEquals($limitCount, $unlimitCount); @@ -371,7 +371,7 @@ public function testReadOverIndexWithLimit() }; $limitCount = count(iterator_to_array($res(10))); - $unlimitCount = count(iterator_to_array($res(null))); + $unlimitCount = count(iterator_to_array($res(0))); $this->assertEquals(10, $limitCount); $this->assertNotEquals($limitCount, $unlimitCount); diff --git a/Spanner/tests/System/SessionTest.php b/Spanner/tests/System/SessionTest.php index 6fe694880053..c75345707ed1 100644 --- a/Spanner/tests/System/SessionTest.php +++ b/Spanner/tests/System/SessionTest.php @@ -38,7 +38,7 @@ public function testCacheSessionPool() $identity['database'] ); - $cache = new MemoryCacheItemPool; + $cache = new MemoryCacheItemPool(); $pool = new CacheSessionPool($cache, [ 'maxSessions' => 10, 'minSessions' => 5, @@ -105,7 +105,7 @@ public function testSessionPoolShouldFailWhenIncorrectDatabase() ['maxCyclesToWaitForSession' => 1] ); $db->runTransaction(function ($t) { - $t->select("SELECT 1"); + $t->select('SELECT 1'); $t->commit(); }); } diff --git a/Spanner/tests/System/SnapshotTest.php b/Spanner/tests/System/SnapshotTest.php index e41dc3c3cb8d..253d9087eb8b 100644 --- a/Spanner/tests/System/SnapshotTest.php +++ b/Spanner/tests/System/SnapshotTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\System; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Timestamp; +use Google\Protobuf\Duration; /** * @group spanner @@ -90,7 +90,7 @@ public function testSnapshotExactTimestampRead() $db->insert(self::$tableName, $row); sleep(1); - $ts = new Timestamp(new \DateTimeImmutable); + $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; @@ -155,14 +155,14 @@ public function testSnapshotExactStaleness() $db->insert(self::$tableName, $row); sleep(1); - $ts = new Timestamp(new \DateTimeImmutable); + $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; $newRow['number'] = 2; $db->replace(self::$tableName, $newRow); - $duration = new Duration(1); + $duration = new Duration(['seconds' => 1, 'nanos' => 0]); $snapshot = $db->snapshot([ 'exactStaleness' => $duration, @@ -190,14 +190,14 @@ public function testSnapshotMaxStaleness() $db->insert(self::$tableName, $row); sleep(1); - $ts = new Timestamp(new \DateTimeImmutable); + $ts = new Timestamp(new \DateTimeImmutable()); sleep(1); $newRow = $row; $newRow['number'] = 2; $db->replace(self::$tableName, $newRow); - $duration = new Duration(1); + $duration = new Duration(['seconds' => 1, 'nanos' => 0]); $snapshot = $db->snapshot([ 'maxStaleness' => $duration, @@ -219,7 +219,7 @@ public function testSnapshotMinReadTimestampFails() $db = self::$database; $db->snapshot([ - 'minReadTimestamp' => new Timestamp(new \DateTimeImmutable) + 'minReadTimestamp' => new Timestamp(new \DateTimeImmutable()) ]); } @@ -233,7 +233,7 @@ public function testSnapshotMaxStalenessFails() $db = self::$database; $db->snapshot([ - 'maxStaleness' => new Duration(1) + 'maxStaleness' => new Duration(['seconds' => 1, 'nanos' => 0]) ]); } diff --git a/Spanner/tests/System/SpannerPgTestCase.php b/Spanner/tests/System/SpannerPgTestCase.php index 5787afd0a69a..79426b24bc6c 100644 --- a/Spanner/tests/System/SpannerPgTestCase.php +++ b/Spanner/tests/System/SpannerPgTestCase.php @@ -17,12 +17,12 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Auth\Cache\MemoryCacheItemPool; use Google\Cloud\Core\Testing\System\SystemTestCase; use Google\Cloud\Spanner; -use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Session\CacheSessionPool; -use Google\Auth\Cache\MemoryCacheItemPool; +use Google\Cloud\Spanner\SpannerClient; /** * @group spanner @@ -106,7 +106,7 @@ public static function getDatabaseFromInstance($instance, $dbName, $options = [] public static function getDatabaseWithSessionPool($dbName, $options = []) { - $sessionCache = new MemoryCacheItemPool; + $sessionCache = new MemoryCacheItemPool(); $sessionPool = new CacheSessionPool( $sessionCache, $options @@ -128,7 +128,7 @@ public static function getDatabaseInstance($dbName) public static function skipEmulatorTests() { - if ((bool) getenv("SPANNER_EMULATOR_HOST")) { + if ((bool) getenv('SPANNER_EMULATOR_HOST')) { self::markTestSkipped('This test is not supported by the emulator.'); } } @@ -183,7 +183,7 @@ private static function getClient() $clientConfig['gapicSpannerInstanceAdminClient'] = new Spanner\Admin\Instance\V1\InstanceAdminClient($gapicConfig); - echo "Using Service Address: ". $serviceAddress . PHP_EOL; + echo 'Using Service Address: ' . $serviceAddress . PHP_EOL; } self::$client = new SpannerClient($clientConfig); diff --git a/Spanner/tests/System/SpannerTestCase.php b/Spanner/tests/System/SpannerTestCase.php index d74467a5237f..c3cc4687771d 100644 --- a/Spanner/tests/System/SpannerTestCase.php +++ b/Spanner/tests/System/SpannerTestCase.php @@ -17,12 +17,12 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Auth\Cache\MemoryCacheItemPool; use Google\Cloud\Core\Testing\System\SystemTestCase; use Google\Cloud\Spanner; -use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Spanner\Session\CacheSessionPool; -use Google\Auth\Cache\MemoryCacheItemPool; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; +use Google\Cloud\Spanner\Session\CacheSessionPool; +use Google\Cloud\Spanner\SpannerClient; /** * @group spanner @@ -125,7 +125,7 @@ private static function getClient() $clientConfig['gapicSpannerInstanceAdminClient'] = new Spanner\Admin\Instance\V1\InstanceAdminClient($gapicConfig); - echo "Using Service Address: ". $serviceAddress . PHP_EOL; + echo 'Using Service Address: ' . $serviceAddress . PHP_EOL; } self::$client = new SpannerClient($clientConfig); @@ -146,7 +146,7 @@ public static function getDatabaseFromInstance($instance, $dbName, $options = [] public static function getDatabaseWithSessionPool($dbName, $options = []) { - $sessionCache = new MemoryCacheItemPool; + $sessionCache = new MemoryCacheItemPool(); $sessionPool = new CacheSessionPool( $sessionCache, $options @@ -163,7 +163,7 @@ public static function getDatabaseWithSessionPool($dbName, $options = []) public static function skipEmulatorTests() { - if ((bool) getenv("SPANNER_EMULATOR_HOST")) { + if ((bool) getenv('SPANNER_EMULATOR_HOST')) { self::markTestSkipped('This test is not supported by the emulator.'); } } diff --git a/Spanner/tests/System/TransactionTest.php b/Spanner/tests/System/TransactionTest.php index 99b9931cb025..968dc122d6b0 100644 --- a/Spanner/tests/System/TransactionTest.php +++ b/Spanner/tests/System/TransactionTest.php @@ -17,9 +17,9 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Spanner\Date; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; @@ -79,7 +79,7 @@ public function testRunTransaction() $row = [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), - 'birthday' => new Date(new \DateTime) + 'birthday' => new Date(new \DateTime()) ]; $cols = array_keys($row); @@ -114,7 +114,6 @@ public function testConcurrentTransactionsIncrementValueWithRead() 'number' => 0 ]); - $iterations = shell_exec(implode(' ', [ 'php', __DIR__ . '/pcntl/ConcurrentTransactionsIncrementValueWithRead.php', @@ -395,7 +394,7 @@ public function testRunTransactionILBWithMultipleOperations() $row = [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), - 'birthday' => new Date(new \DateTime) + 'birthday' => new Date(new \DateTime()) ]; // Representative of all mutations $t->insert(self::TEST_TABLE_NAME, $row); @@ -408,7 +407,7 @@ public function testRunTransactionILBWithMultipleOperations() 'parameters' => [ 'id' => $id, 'name' => uniqid(self::TESTING_PREFIX), - 'birthday' => new Date(new \DateTime) + 'birthday' => new Date(new \DateTime()) ] ] ); diff --git a/Spanner/tests/System/WriteTest.php b/Spanner/tests/System/WriteTest.php index e6724b7111f6..193559ffd30f 100644 --- a/Spanner/tests/System/WriteTest.php +++ b/Spanner/tests/System/WriteTest.php @@ -25,8 +25,8 @@ use Google\Cloud\Spanner\CommitTimestamp; use Google\Cloud\Spanner\Date; use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Numeric; +use Google\Cloud\Spanner\Timestamp; use Google\Rpc\Code; /** @@ -83,7 +83,7 @@ public function fieldValueProvider() return [ [$this->randId(), 'boolField', false], [$this->randId(), 'boolField', true], - [$this->randId(), 'arrayField', [1,2,3,4,5]], + [$this->randId(), 'arrayField', [1, 2, 3, 4, 5]], [$this->randId(), 'dateField', new Date(new \DateTime('1981-01-20'))], [$this->randId(), 'floatField', 3.1415], [$this->randId(), 'floatField', INF], @@ -93,7 +93,7 @@ public function fieldValueProvider() [$this->randId(), 'float32Field', -INF], [$this->randId(), 'intField', 787878787], [$this->randId(), 'stringField', 'foo bar'], - [$this->randId(), 'timestampField', new Timestamp(new \DateTime)], + [$this->randId(), 'timestampField', new Timestamp(new \DateTime())], [$this->randId(), 'numericField', new Numeric('0.123456789')] ]; } @@ -255,9 +255,9 @@ public function arrayFieldValueProvider() { return [ [$this->randId(), 'arrayField', []], - [$this->randId(), 'arrayField', [1,2,null,4,5]], + [$this->randId(), 'arrayField', [1, 2, null, 4, 5]], [$this->randId(), 'arrayField', null], - [$this->randId(), 'arrayBoolField', [true,false]], + [$this->randId(), 'arrayBoolField', [true, false]], [$this->randId(), 'arrayBoolField', []], [$this->randId(), 'arrayBoolField', [true, false, null, false]], [$this->randId(), 'arrayBoolField', null], @@ -269,9 +269,9 @@ public function arrayFieldValueProvider() [$this->randId(), 'arrayFloat32Field', []], [$this->randId(), 'arrayFloat32Field', [1.1, null, 1.3]], [$this->randId(), 'arrayFloat32Field', null], - [$this->randId(), 'arrayStringField', ['foo','bar','baz']], + [$this->randId(), 'arrayStringField', ['foo', 'bar', 'baz']], [$this->randId(), 'arrayStringField', []], - [$this->randId(), 'arrayStringField', ['foo',null,'baz']], + [$this->randId(), 'arrayStringField', ['foo', null, 'baz']], [$this->randId(), 'arrayStringField', null], [$this->randId(), 'arrayBytesField', []], [$this->randId(), 'arrayBytesField', null], @@ -342,11 +342,12 @@ public function testWriteAndReadBackFancyArrayValue($id, $field, $value) public function arrayFieldComplexValueProvider() { + $timestamp = new Timestamp(new \DateTime()); return [ - [$this->randId(), 'arrayBytesField', [new Bytes('foo'),null,new Bytes('baz')]], - [$this->randId(), 'arrayTimestampField', [new Timestamp(new \DateTime),null,new Timestamp(new \DateTime)]], - [$this->randId(), 'arrayDateField', [new Date(new \DateTime),null,new Date(new \DateTime)]], - [$this->randId(), 'arrayNumericField', [new Numeric("0.12345"),null,new NUMERIC("12345")]], + [$this->randId(), 'arrayBytesField', [new Bytes('foo'), null, new Bytes('baz')]], + [$this->randId(), 'arrayTimestampField', [$timestamp, null, $timestamp]], + [$this->randId(), 'arrayDateField', [new Date(new \DateTime()), null, new Date(new \DateTime())]], + [$this->randId(), 'arrayNumericField', [new Numeric('0.12345'), null, new NUMERIC('12345')]], ]; } @@ -480,11 +481,11 @@ public function randomNumericProvider() } return [ - [$this->randId(), new Numeric((string)rand(100, 9999))], - [$this->randId(), new Numeric((string)rand(100, 9999))], - [$this->randId(), new Numeric((string)rand(100, 9999))], - [$this->randId(), new Numeric((string)rand(100, 9999))], - [$this->randId(), new Numeric((string)rand(100, 9999))], + [$this->randId(), new Numeric((string) rand(100, 9999))], + [$this->randId(), new Numeric((string) rand(100, 9999))], + [$this->randId(), new Numeric((string) rand(100, 9999))], + [$this->randId(), new Numeric((string) rand(100, 9999))], + [$this->randId(), new Numeric((string) rand(100, 9999))], ]; } @@ -496,7 +497,7 @@ public function testCommitTimestamp() $id = $this->randId(); $ts = self::$database->insert(self::COMMIT_TIMESTAMP_TABLE_NAME, [ 'id' => $id, - 'commitTimestamp' => new CommitTimestamp + 'commitTimestamp' => new CommitTimestamp() ]); $res = self::$database->execute('SELECT * FROM ' . self::COMMIT_TIMESTAMP_TABLE_NAME . ' WHERE id = @id', [ @@ -621,7 +622,7 @@ public function testTimestampPrecisionLocale($timestamp) public function timestamps() { - $today = new \DateTime; + $today = new \DateTime(); $str = $today->format('Y-m-d\TH:i:s'); $todayLowMs = \DateTime::createFromFormat('U.u', time() . '.012345'); @@ -649,17 +650,17 @@ public function testExecuteUpdate() $db = self::$database; $db->runTransaction(function ($t) use ($id, $randStr) { - $count = $t->executeUpdate( - 'INSERT INTO ' . self::TABLE_NAME . ' (id, stringField) VALUES (@id, @string)', - [ - 'parameters' => [ - 'id' => $id, - 'string' => $randStr - ] + $count = $t->executeUpdate( + 'INSERT INTO ' . self::TABLE_NAME . ' (id, stringField) VALUES (@id, @string)', + [ + 'parameters' => [ + 'id' => $id, + 'string' => $randStr ] - ); + ] + ); - $this->assertEquals(1, $count); + $this->assertEquals(1, $count); $row = $t->execute('SELECT * FROM ' . self::TABLE_NAME . ' WHERE id = @id', [ 'parameters' => [ diff --git a/Spanner/tests/System/pcntl/AbortedErrorCausesRetry.php b/Spanner/tests/System/pcntl/AbortedErrorCausesRetry.php index 590cd55fd0a2..9dfbb3639f86 100644 --- a/Spanner/tests/System/pcntl/AbortedErrorCausesRetry.php +++ b/Spanner/tests/System/pcntl/AbortedErrorCausesRetry.php @@ -6,7 +6,7 @@ use Google\Cloud\Core\Exception\AbortedException; use Google\Cloud\Spanner\Tests\System\SpannerTestCase; -list ($dbName, $tableName, $id) = getInputArgs(); +list($dbName, $tableName, $id) = getInputArgs(); $delay = 5000; if ($childPID1 = pcntl_fork()) { diff --git a/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithExecute.php b/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithExecute.php index 7f14099acacd..37b8cba7c27f 100644 --- a/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithExecute.php +++ b/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithExecute.php @@ -5,7 +5,7 @@ use Google\Cloud\Spanner\Tests\System\SpannerTestCase; -list ($dbName, $tableName, $id) = getInputArgs(); +list($dbName, $tableName, $id) = getInputArgs(); $tmpFile = sys_get_temp_dir() . '/ConcurrentTransactionsIncremementValueWithExecute.txt'; setupIterationTracker($tmpFile); @@ -22,7 +22,7 @@ ] ])->rows()->current(); - $row['number'] +=1; + $row['number'] += 1; $transaction->update($tableName, $row); $transaction->commit(); diff --git a/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithRead.php b/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithRead.php index 7fd596ac369f..5355d50bcc15 100644 --- a/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithRead.php +++ b/Spanner/tests/System/pcntl/ConcurrentTransactionsIncrementValueWithRead.php @@ -6,13 +6,13 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Tests\System\SpannerTestCase; -list ($dbName, $tableName, $id) = getInputArgs(); +list($dbName, $tableName, $id) = getInputArgs(); $tmpFile = sys_get_temp_dir() . '/ConcurrentTransactionsIncremementValueWithRead.txt'; setupIterationTracker($tmpFile); $keyset = new KeySet(['keys' => [$id]]); -$columns = ['id','number']; +$columns = ['id', 'number']; $callable = function ($dbName, KeySet $keyset, array $columns, $tableName) use ($tmpFile) { $iterations = 0; @@ -21,7 +21,7 @@ $iterations++; $row = $transaction->read($tableName, $keyset, $columns)->rows()->current(); - $row['number'] +=1; + $row['number'] += 1; $transaction->update($tableName, $row); $transaction->commit(); diff --git a/Spanner/tests/Unit/Admin/Database/V1/Client/DatabaseAdminClientTest.php b/Spanner/tests/Unit/Admin/Database/V1/Client/DatabaseAdminClientTest.php index 76c5c46c6a10..85b63a46f722 100644 --- a/Spanner/tests/Unit/Admin/Database/V1/Client/DatabaseAdminClientTest.php +++ b/Spanner/tests/Unit/Admin/Database/V1/Client/DatabaseAdminClientTest.php @@ -1,6 +1,6 @@ <?php /* - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ use Google\ApiCore\ApiException; use Google\ApiCore\CredentialsWrapper; -use Google\ApiCore\LongRunning\OperationsClient; use Google\ApiCore\Testing\GeneratedTest; use Google\ApiCore\Testing\MockTransport; use Google\Cloud\Iam\V1\GetIamPolicyRequest; @@ -66,6 +65,7 @@ use Google\Cloud\Spanner\Admin\Database\V1\UpdateBackupScheduleRequest; use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseRequest; +use Google\LongRunning\Client\OperationsClient; use Google\LongRunning\GetOperationRequest; use Google\LongRunning\Operation; use Google\Protobuf\Any; diff --git a/Spanner/tests/Unit/Admin/Instance/V1/Client/InstanceAdminClientTest.php b/Spanner/tests/Unit/Admin/Instance/V1/Client/InstanceAdminClientTest.php index 1d694ac1b6d9..f8b22ace92a7 100644 --- a/Spanner/tests/Unit/Admin/Instance/V1/Client/InstanceAdminClientTest.php +++ b/Spanner/tests/Unit/Admin/Instance/V1/Client/InstanceAdminClientTest.php @@ -1,6 +1,6 @@ <?php /* - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ use Google\ApiCore\ApiException; use Google\ApiCore\CredentialsWrapper; -use Google\ApiCore\LongRunning\OperationsClient; use Google\ApiCore\Testing\GeneratedTest; use Google\ApiCore\Testing\MockTransport; use Google\Cloud\Iam\V1\GetIamPolicyRequest; @@ -60,6 +59,7 @@ use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstancePartitionRequest; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; +use Google\LongRunning\Client\OperationsClient; use Google\LongRunning\GetOperationRequest; use Google\LongRunning\Operation; use Google\Protobuf\Any; diff --git a/Spanner/tests/Unit/ArrayTypeTest.php b/Spanner/tests/Unit/ArrayTypeTest.php index d04b0f2d7fd2..b24199bfd6aa 100644 --- a/Spanner/tests/Unit/ArrayTypeTest.php +++ b/Spanner/tests/Unit/ArrayTypeTest.php @@ -72,7 +72,7 @@ public function testArrayType($type) public function testArrayTypeStruct() { - $struct = new StructType; + $struct = new StructType(); $struct->add('foo', Database::TYPE_STRING); $arr = new ArrayType($struct); diff --git a/Spanner/tests/Unit/BackupTest.php b/Spanner/tests/Unit/BackupTest.php index b02926a3155d..71f2cad572af 100644 --- a/Spanner/tests/Unit/BackupTest.php +++ b/Spanner/tests/Unit/BackupTest.php @@ -17,22 +17,24 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\ApiCore\ApiException; +use DateTime; +use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Google\ApiCore\OperationResponse; -use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\Iam\Iam; -use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\DeleteBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateBackupRequest; use Google\Cloud\Spanner\Backup; -use Google\Cloud\Spanner\Connection\ConnectionInterface; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; -use Google\Cloud\Spanner\Timestamp; +use Google\Protobuf\FieldMask; +use Google\Protobuf\Timestamp; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -45,6 +47,8 @@ class BackupTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; + use ArraySubsetAsserts; + use ApiHelperTrait; const PROJECT_ID = 'test-project'; const INSTANCE = 'instance-name'; @@ -52,202 +56,294 @@ class BackupTest extends TestCase const BACKUP = 'backup-name'; const COPIED_BACKUP = 'new-backup-name'; - private $connection; + private $databaseAdminClient; + private Serializer $serializer; private $instance; + private $operationResponse; + private $database; - private $lro; - private $lroCallables; - private $expireTime; - private $createTime; + private DateTime $expireTime; private $versionTime; - private $backup; - private $copiedBackup; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->prophesize(ConnectionInterface::class); - $this->instance = $this->prophesize(Instance::class); + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->serializer = new Serializer(); + $this->database = $this->prophesize(Database::class); $this->database->name()->willReturn( DatabaseAdminClient::databaseName(self::PROJECT_ID, self::INSTANCE, self::DATABASE) ); - $this->instance->name()->willReturn(InstanceAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE)); + + $this->instance = $this->prophesize(Instance::class); + $this->instance->name()->willReturn(DatabaseAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE)); $this->instance->database(Argument::any())->willReturn($this->database); - $this->lro = $this->prophesize(LongRunningConnectionInterface::class); - $this->lroCallables = []; - $this->expireTime = new \DateTime("+ 7 hours"); - $this->createTime = $this->expireTime; - $this->versionTime = new \DateTime("- 2 hours"); - - $args=[ - $this->connection->reveal(), - $this->instance->reveal(), - $this->lro->reveal(), - $this->lroCallables, - self::PROJECT_ID, - self::BACKUP - ]; - $props = [ - 'instance', 'connection' - ]; - $this->backup = TestHelpers::stub(Backup::class, $args, $props); - // copiedBackup will contain a mock of the backup object where - // $backup will be copied into - $copyArgs = $args; - $copyArgs[5] = self::COPIED_BACKUP; - $this->copiedBackup = TestHelpers::stub(Backup::class, $copyArgs, $props); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + + $this->expireTime = new DateTime('+7 hours'); + $this->versionTime = new DateTime('-2 hours'); } public function testName() { + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); + $this->assertEquals( DatabaseAdminClient::backupName(self::PROJECT_ID, self::INSTANCE, self::BACKUP), - $this->backup->name() + $backup->name() ); } public function testCreate() { - $this->connection->createBackup(Argument::allOf( - Argument::withEntry('instance', InstanceAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE)), - Argument::withEntry('backupId', self::BACKUP), - Argument::withEntry('backup', [ - 'database' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::INSTANCE, self::DATABASE), - 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z'), - ]), - Argument::withEntry('versionTime', $this->versionTime->format('Y-m-d\TH:i:s.u\Z')) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->backup->___setProperty('connection', $this->connection->reveal()); - $op = $this->backup->create(self::DATABASE, $this->expireTime, [ + $expected = [ + 'parent' => DatabaseAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE), + 'database' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::INSTANCE, self::DATABASE), + 'expire_time' => $this->expireTime->format('U'), + 'version_time' => $this->versionTime->format('U'), + ]; + $this->databaseAdminClient->createBackup( + Argument::that(function (CreateBackupRequest $request) use ($expected) { + $this->assertEquals($expected['parent'], $request->getParent()); + $this->assertEquals(self::BACKUP, $request->getBackupId()); + $this->assertEquals($expected['database'], $request->getBackup()->getDatabase()); + $this->assertEquals($expected['expire_time'], $request->getBackup()->getExpireTime()->getSeconds()); + $this->assertEquals($expected['version_time'], $request->getBackup()->getVersionTime()->getSeconds()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); + + $operation = $backup->create(self::DATABASE, $this->expireTime, [ 'versionTime' => $this->versionTime, ]); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $operation); } public function testCreateCopy() { - $this->connection->copyBackup(Argument::allOf( - Argument::withEntry('instance', InstanceAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE)), - Argument::withEntry('backupId', self::COPIED_BACKUP), - Argument::withKey('sourceBackupId'), - Argument::withEntry('expireTime', $this->expireTime->format('Y-m-d\TH:i:s.u\Z')) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->backup->___setProperty('connection', $this->connection->reveal()); - $op = $this->backup->createCopy($this->copiedBackup, $this->expireTime); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $expected = [ + 'parent' => DatabaseAdminClient::instanceName(self::PROJECT_ID, self::INSTANCE), + 'source_backup' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::INSTANCE, self::BACKUP), + 'expire_time' => $this->expireTime->format('U'), + ]; + $this->databaseAdminClient->copyBackup( + Argument::that(function (CopyBackupRequest $request) use ($expected) { + $this->assertEquals($expected['parent'], $request->getParent()); + $this->assertEquals(self::COPIED_BACKUP, $request->getBackupId()); + $this->assertEquals($expected['source_backup'], $request->getSourceBackup()); + $this->assertEquals($expected['expire_time'], $request->getExpireTime()->getSeconds()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); + + $copiedBackup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::COPIED_BACKUP + ); + + $op = $backup->createCopy($copiedBackup, $this->expireTime); + $this->assertInstanceOf(OperationResponse::class, $op); } public function testDelete() { - $this->connection->deleteBackup(Argument::withEntry('name', $this->backup->name())) - ->shouldBeCalled(); - - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->deleteBackup( + Argument::type(DeleteBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); - $this->backup->delete(); + $backup->delete(); } public function testInfo() { - $res = [ - 'name' => $this->backup->name(), - 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z'), - 'createTime' => $this->createTime->format('Y-m-d\TH:i:s.u\Z'), - 'versionTime' => $this->versionTime->format('Y-m-d\TH:i:s.u\Z') - ]; - - $this->connection->getBackup(Argument::withEntry('name', $this->backup->name())) - ->shouldBeCalledTimes(1) - ->willReturn($res); + $response = new BackupProto([ + 'name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::INSTANCE, self::BACKUP), + 'expire_time' => new Timestamp(['seconds' => $this->expireTime->format('U')]), + 'create_time' => new Timestamp(['seconds' => $this->expireTime->format('U')]), + 'version_time' => new Timestamp(['seconds' => $this->versionTime->format('U')]), + ]); - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($response); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); - $info = $this->backup->info(); + $info = $backup->info(); - $this->assertEquals($res, $info); + $this->assertArraySubset([ + 'name' => $response->getName(), + 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.000000\Z'), + 'createTime' => $this->expireTime->format('Y-m-d\TH:i:s.000000\Z'), + 'versionTime' => $this->versionTime->format('Y-m-d\TH:i:s.000000\Z'), + ], $info); // Make sure the request only is sent once. - $this->backup->info(); + $backup->info(); } public function testReload() { - $res = [ - 'name' => $this->backup->name(), - 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z'), - 'createTime' => $this->createTime->format('Y-m-d\TH:i:s.u\Z'), - 'versionTime' => $this->versionTime->format('Y-m-d\TH:i:s.u\Z') - ]; - - $this->connection->getBackup(Argument::withEntry('name', $this->backup->name())) - ->shouldBeCalled() - ->willReturn($res); + $response = new BackupProto([ + 'name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::INSTANCE, self::BACKUP), + ]); - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($response); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP, + ['name' => 'different-name'] + ); - $info = $this->backup->reload(); + $info = $backup->reload(); - $this->assertEquals($res, $info); + $this->assertArraySubset([ + 'name' => $response->getName(), + ], $info); } public function testState() { - $res = [ - 'state' => Backup::STATE_READY - ]; - $this->connection->getBackup(Argument::withEntry('name', $this->backup->name())) - ->shouldBeCalledTimes(1) - ->willReturn($res); + $response = new BackupProto([ + 'state' => Backup::STATE_READY, + ]); - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($response); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); - $this->assertEquals(Backup::STATE_READY, $this->backup->state()); + $this->assertEquals(Backup::STATE_READY, $backup->state()); // Make sure the request only is sent once. - $this->backup->state(); + $backup->state(); } public function testExists() { - $this->connection->getBackup(Argument::withEntry('name', $this->backup->name())) - ->shouldBeCalled() - ->willReturn([]); - - $this->backup->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getBackup( + Argument::type(GetBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new BackupProto()); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); - $this->assertTrue($this->backup->exists()); + $this->assertTrue($backup->exists()); } public function testUpdateExpireTime() { - $res = ['name' => 'foo', 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z')]; - - $this->connection->updateBackup(Argument::allOf( - Argument::withEntry('backup', [ - 'name' => $this->backup->name(), - 'expireTime' => $this->expireTime->format('Y-m-d\TH:i:s.u\Z') - ]), - Argument::withEntry('updateMask', ['paths' => ['expire_time']]) - )) - ->shouldBeCalled() - ->willReturn($res); - - $this->backup->___setProperty('connection', $this->connection->reveal()); - - $info = $this->backup->updateExpireTime($this->expireTime); - $this->assertEquals($res, $info); + $newExpireTime = new DateTime('+1 day'); + + $response = new BackupProto([ + 'name' => 'foo', + 'expire_time' => new Timestamp(['seconds' => $newExpireTime->format('U')]), + ]); + + $this->databaseAdminClient->updateBackup( + Argument::that(function (UpdateBackupRequest $request) use ($newExpireTime) { + $this->assertEquals(new FieldMask(['paths' => ['expire_time']]), $request->getUpdateMask()); + $this->assertEquals($newExpireTime->format('U'), $request->getBackup()->getExpireTime()->getSeconds()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($response); + + $backup = new Backup( + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance->reveal(), + self::PROJECT_ID, + self::BACKUP + ); + + $info = $backup->updateExpireTime($newExpireTime); + $this->assertArraySubset([ + 'expireTime' => $newExpireTime->format('Y-m-d\TH:i:s.000000\Z'), + ], $info); } } diff --git a/Spanner/tests/Unit/Batch/BatchClientTest.php b/Spanner/tests/Unit/Batch/BatchClientTest.php index 3cb3b0b262f6..ae6d56fc11d4 100644 --- a/Spanner/tests/Unit/Batch/BatchClientTest.php +++ b/Spanner/tests/Unit/Batch/BatchClientTest.php @@ -17,7 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit\Batch; -use Google\Cloud\Core\Testing\TestHelpers; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Timestamp; use Google\Cloud\Core\TimeTrait; use Google\Cloud\Spanner\Batch\BatchClient; @@ -26,13 +27,16 @@ use Google\Cloud\Spanner\Batch\ReadPartition; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\Session; +use Google\Cloud\Spanner\V1\Transaction; +use Google\Protobuf\Timestamp as TimestampProto; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use Google\Cloud\Spanner\SpannerClient; /** * @group spanner @@ -41,56 +45,59 @@ */ class BatchClientTest extends TestCase { - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; use TimeTrait; + use ApiHelperTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; - private $connection; - private $client; + private $spannerClient; + private $serializer; + private $batchClient; public function setUp(): void { - $this->connection = $this->getConnStub(); - $this->client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->connection->reveal(), false), + $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(GapicSpannerClient::class); + $this->batchClient = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ], [ - 'operation' - ]); + ); } public function testSnapshot() { $time = time(); + $this->spannerClient->createSession( + Argument::that(function (CreateSessionRequest $request) { + $this->assertEquals( + $request->getDatabase(), + self::DATABASE + ); + return true; + }), + Argument::type('array') + )->shouldBeCalledOnce()->willReturn(new Session(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals( + $this->serializer->encodeMessage($request)['options']['readOnly'], + ['returnReadTimestamp' => true] + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Transaction([ + 'id' => self::TRANSACTION, + 'read_timestamp' => new TimestampProto(['seconds' => $time]) + ])); - $this->connection->createSession(Argument::withEntry('database', self::DATABASE)) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('session', self::SESSION), - Argument::that(function (array $args) { - if ($args['transactionOptions']['readOnly']['returnReadTimestamp'] !== true) { - return false; - } - - return $args['database'] === self::DATABASE; - }) - ))->shouldBeCalled()->willReturn([ - 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ]); - - $this->refreshOperation($this->client, $this->connection->reveal()); - - $snapshot = $this->client->snapshot(); + $snapshot = $this->batchClient->snapshot(); $this->assertInstanceOf(BatchSnapshot::class, $snapshot); } @@ -104,7 +111,7 @@ public function testSnapshotFromString() 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) ])); - $snapshot = $this->client->snapshotFromString($identifier); + $snapshot = $this->batchClient->snapshotFromString($identifier); $this->assertEquals(self::SESSION, $snapshot->session()->name()); $this->assertEquals(self::TRANSACTION, $snapshot->id()); $this->assertEquals( @@ -122,7 +129,7 @@ public function testQueryPartitionFromString() $partition = new QueryPartition($token, $sql, $options); $string = (string) $partition; - $res = $this->client->partitionFromString($partition); + $res = $this->batchClient->partitionFromString($partition); $this->assertEquals($token, $res->token()); $this->assertEquals($sql, $res->sql()); $this->assertEquals($options, $res->options()); @@ -133,13 +140,13 @@ public function testReadPartitionFromString() $token = 'foobar'; $table = 'table'; $keyset = new KeySet(['all' => true]); - $columns = ['a','b']; + $columns = ['a', 'b']; $options = ['hello' => 'world']; $partition = new ReadPartition($token, $table, $keyset, $columns, $options); $string = (string) $partition; - $res = $this->client->partitionFromString($partition); + $res = $this->batchClient->partitionFromString($partition); $this->assertEquals($token, $res->token()); $this->assertEquals($table, $res->table()); $this->assertEquals($keyset->keySetObject(), $res->keySet()->keySetObject()); @@ -153,7 +160,7 @@ public function testMissingPartitionTypeKey() $this->expectExceptionMessage('Invalid partition data.'); $data = base64_encode(json_encode(['hello' => 'world'])); - $this->client->partitionFromString($data); + $this->batchClient->partitionFromString($data); } public function testInvalidPartitionType() @@ -162,36 +169,43 @@ public function testInvalidPartitionType() $this->expectExceptionMessage('Invalid partition type.'); $data = base64_encode(json_encode([BatchClient::PARTITION_TYPE_KEY => uniqid('this-is-not-real')])); - $this->client->partitionFromString($data); + $this->batchClient->partitionFromString($data); } public function testSnapshotDatabaseRole() { $time = time(); + $this->spannerClient->createSession( + Argument::that(function (CreateSessionRequest $request) { + return $this->serializer->encodeMessage($request)['session']['creatorRole'] == 'Reader'; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Session(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals( + $this->serializer->encodeMessage($request)['options']['readOnly'], + ['returnReadTimestamp' => true] + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Transaction([ + 'id' => self::TRANSACTION, + 'read_timestamp' => new TimestampProto(['seconds' => $time]) + ])); - $client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->connection->reveal(), false), + $batchClient = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE, ['databaseRole' => 'Reader'] - ], [ - 'operation' - ]); + ); - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled()->willReturn([ - 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ]); - - $this->connection->createSession(Argument::withEntry( - 'session', - ['labels' => [], 'creator_role' => 'Reader'] - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - - $snapshot = $client->snapshot(); + $snapshot = $batchClient->snapshot(); } } diff --git a/Spanner/tests/Unit/Batch/BatchSnapshotTest.php b/Spanner/tests/Unit/Batch/BatchSnapshotTest.php index 84717d6f873f..11c13acd2507 100644 --- a/Spanner/tests/Unit/Batch/BatchSnapshotTest.php +++ b/Spanner/tests/Unit/Batch/BatchSnapshotTest.php @@ -17,7 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit\Batch; -use Google\Cloud\Core\Testing\TestHelpers; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\PartitionInterface; use Google\Cloud\Spanner\Batch\QueryPartition; @@ -26,11 +27,16 @@ use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\V1\SpannerClient; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionReadRequest; +use Google\Cloud\Spanner\V1\PartitionResponse; +use Google\Cloud\Spanner\V1\ReadRequest; +use Google\Cloud\Spanner\V1\KeySet as KeySetProto; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -42,18 +48,18 @@ */ class BatchSnapshotTest extends TestCase { - use OperationRefreshTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; + use ApiHelperTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; const TRANSACTION = 'transaction-id'; + private $spannerClient; + private $serializer; private $session; private $timestamp; - private $connection; private $snapshot; public function setUp(): void @@ -68,29 +74,51 @@ public function setUp(): void $this->timestamp = new Timestamp(new \DateTime()); - $this->connection = $this->getConnStub(); - $this->snapshot = TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->connection->reveal(), false), + $this->serializer = new Serializer([], [], [], [ + 'google.spanner.v1.KeySet' => function ($keySet) { + $keys = $this->pluck('keys', $keySet, false); + if ($keys) { + $keySet['keys'] = array_map( + fn ($key) => $this->formatListForApi((array) $key), + $keys + ); + } + + if (isset($keySet['ranges'])) { + $keySet['ranges'] = array_map(function ($rangeItem) { + return array_map([$this, 'formatListForApi'], $rangeItem); + }, $keySet['ranges']); + + if (empty($keySet['ranges'])) { + unset($keySet['ranges']); + } + } + return $keySet; + }, + ]); + $this->spannerClient = $this->prophesize(SpannerClient::class); + + $this->snapshot = new BatchSnapshot( + new Operation($this->spannerClient->reveal(), $this->serializer, false), $this->session->reveal(), ['id' => self::TRANSACTION, 'readTimestamp' => $this->timestamp] - ], [ - 'operation', 'session' - ]); + ); } public function testClose() { $session = $this->prophesize(Session::class); - $session->delete([])->shouldBeCalled(); + $session->delete([])->shouldBeCalledOnce(); + + $this->snapshot = new BatchSnapshot( + $this->prophesize(Operation::class)->reveal(), + $session->reveal() + ); - $this->snapshot->___setProperty('session', $session->reveal()); $this->snapshot->close(); } - /** - * @dataProvider partitionReadAndQueryOptions - */ - public function testPartitionRead($testCaseOptions) + public function testPartitionRead() { $table = 'table'; $keySet = new KeySet(['all' => true]); @@ -99,15 +127,14 @@ public function testPartitionRead($testCaseOptions) 'index' => 'foo', 'maxPartitions' => 10, 'partitionSizeBytes' => 1 - ] + $testCaseOptions; + ]; $expectedArguments = [ 'session' => self::SESSION, - 'database' => self::DATABASE, - 'transactionId' => self::TRANSACTION, + 'transaction' => ['id' => self::TRANSACTION], 'table' => $table, 'columns' => $columns, - 'keySet' => $keySet->keySetObject(), + 'keySet' => $keySet->keySetObject() + ['keys' => [], 'ranges' => []], 'index' => $opts['index'], 'partitionOptions' => [ 'maxPartitions' => $opts['maxPartitions'], @@ -115,23 +142,19 @@ public function testPartitionRead($testCaseOptions) ] ]; - $expectedArguments += $testCaseOptions; - - $this->connection->partitionRead(Argument::that( - function ($actualArguments) use ($expectedArguments) { + $this->spannerClient->partitionRead( + Argument::that(function (PartitionReadRequest $request) use ($expectedArguments) { + $actualArguments = $this->serializer->encodeMessage($request); + // var_dump($actualArguments, $expectedArguments);exit; return $actualArguments == $expectedArguments; - } - ))->shouldBeCalled()->willReturn([ + }), + Argument::type('array') + )->shouldBeCalledOnce()->willReturn(new PartitionResponse([ 'partitions' => [ - [ - 'partitionToken' => 'token1' - ], [ - 'partitionToken' => 'token2' - ] + new Partition(['partition_token' => 'token1']), + new Partition(['partition_token' => 'token2']) ] - ]); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + ])); $partitions = $this->snapshot->partitionRead($table, $keySet, $columns, $opts); $this->assertContainsOnlyInstancesOf(ReadPartition::class, $partitions); @@ -142,10 +165,7 @@ function ($actualArguments) use ($expectedArguments) { $this->assertEquals($opts, $partitions[0]->options()); } - /** - * @dataProvider partitionReadAndQueryOptions - */ - public function testPartitionQuery(array $testCaseOptions) + public function testPartitionQuery() { $sql = 'SELECT 1=1'; $opts = [ @@ -154,38 +174,32 @@ public function testPartitionQuery(array $testCaseOptions) ], 'maxPartitions' => 10, 'partitionSizeBytes' => 1 - ] + $testCaseOptions; + ]; $expectedArguments = [ 'session' => self::SESSION, - 'database' => self::DATABASE, - 'transactionId' => self::TRANSACTION, + 'transaction' => ['id' => self::TRANSACTION], 'sql' => $sql, 'params' => $opts['parameters'], - 'paramTypes' => ['foo' => ['code' => 6]], + 'paramTypes' => ['foo' => ['code' => 6, 'typeAnnotation' => 0, 'protoTypeFqn' => '']], 'partitionOptions' => [ 'maxPartitions' => $opts['maxPartitions'], 'partitionSizeBytes' => $opts['partitionSizeBytes'] ] ]; - $expectedArguments += $testCaseOptions; - - $this->connection->partitionQuery(Argument::that( - function ($actualArguments) use ($expectedArguments) { + $this->spannerClient->partitionQuery( + Argument::that(function (PartitionQueryRequest $request) use ($expectedArguments) { + $actualArguments = $this->serializer->encodeMessage($request); return $actualArguments == $expectedArguments; - } - ))->shouldBeCalled()->willReturn([ - 'partitions' => [ - [ - 'partitionToken' => 'token1' - ], [ - 'partitionToken' => 'token2' + }), + Argument::type('array') + )->shouldBeCalledOnce()->willReturn(new PartitionResponse([ + 'partitions' => [ + new Partition(['partition_token' => 'token1']), + new Partition(['partition_token' => 'token2']) ] - ] - ]); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + ])); $partitions = $this->snapshot->partitionQuery($sql, $opts); $this->assertContainsOnlyInstancesOf(QueryPartition::class, $partitions); @@ -209,17 +223,25 @@ public function testExecuteQueryPartition() $partition = new QueryPartition($token, $sql, $opts); - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('partitionToken', $token), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('sql', $sql), - Argument::withEntry('params', $opts['parameters']), - Argument::withEntry('paramTypes', ['foo' => ['code' => 6]]) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql, $opts, $token) { + $this->assertEquals($request->getSql(), $sql); + $this->assertEquals($request->getSession(), self::SESSION); + $this->assertEquals($request->getTransaction()->getId(), self::TRANSACTION); + $this->assertEquals($request->getPartitionToken(), $token); + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['params'], $opts['parameters']); + $this->assertEquals( + $message['paramTypes'], + ['foo' => ['code' => 6, 'typeAnnotation' => 0, 'protoTypeFqn' => '']] + ); + return true; + }), + Argument::type('array') + )->shouldBeCalledOnce()->willReturn( + $this->resultGeneratorStream() + ); + $res = $this->snapshot->executePartition($partition); $this->assertInstanceOf(Result::class, $res); $rows = iterator_to_array($res->rows()); @@ -238,18 +260,25 @@ public function testExecuteReadPartition() $partition = new ReadPartition($token, $table, $keySet, $columns, $opts); - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('partitionToken', $token), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('table', $table), - Argument::withEntry('columns', $columns), - Argument::withEntry('keySet', $keySet->keySetObject()), - Argument::withEntry('index', $opts['index']) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->snapshot, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function (ReadRequest $request) use ($token, $table, $columns, $keySet, $opts) { + $this->assertEquals($request->getSession(), self::SESSION); + $this->assertEquals($request->getPartitionToken(), $token); + $this->assertEquals($request->getTable(), $table); + $this->assertEquals($request->getIndex(), $opts['index']); + $this->assertEquals(iterator_to_array($request->getColumns()), $columns); + $this->assertEquals( + $request->getTransaction()->getId(), + self::TRANSACTION + ); + $this->assertTrue($this->serializer->encodeMessage($request->getKeySet())['all']); + return true; + }), + Argument::type('array') + )->shouldBeCalledOnce()->willReturn( + $this->resultGeneratorStream() + ); + $res = $this->snapshot->executePartition($partition); $this->assertInstanceOf(Result::class, $res); $rows = iterator_to_array($res->rows()); @@ -273,24 +302,22 @@ public function testExecutePartitionInvalidType() $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Unsupported partition type.'); - $dummy = new DummyPartition; + $dummy = new DummyPartition(); $this->snapshot->executePartition($dummy); } - - public function partitionReadAndQueryOptions() - { - return [ - [['dataBoostEnabled' => false]], - [['dataBoostEnabled' => true]] - ]; - } } //@codingStandardsIgnoreStart class DummyPartition implements PartitionInterface { - public function __toString() {} - public function serialize() {} - public static function hydrate(array $data) {} + public function __toString() + { + } + public function serialize() + { + } + public static function hydrate(array $data) + { + } } //@codingStandardsIgnoreEnd diff --git a/Spanner/tests/Unit/BytesTest.php b/Spanner/tests/Unit/BytesTest.php index e923a72c702e..104276ee2685 100644 --- a/Spanner/tests/Unit/BytesTest.php +++ b/Spanner/tests/Unit/BytesTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Spanner\Bytes; use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\Bytes; use PHPUnit\Framework\TestCase; /** diff --git a/Spanner/tests/Unit/CommitTimestampTest.php b/Spanner/tests/Unit/CommitTimestampTest.php index 782de7af676f..9dd2fc9ef6a6 100644 --- a/Spanner/tests/Unit/CommitTimestampTest.php +++ b/Spanner/tests/Unit/CommitTimestampTest.php @@ -30,7 +30,7 @@ class CommitTimestampTest extends TestCase public function setUp(): void { - $this->t = new CommitTimestamp; + $this->t = new CommitTimestamp(); } public function testType() diff --git a/Spanner/tests/Unit/Connection/GrpcTest.php b/Spanner/tests/Unit/Connection/GrpcTest.php deleted file mode 100644 index 4b42ecc0d1d3..000000000000 --- a/Spanner/tests/Unit/Connection/GrpcTest.php +++ /dev/null @@ -1,1690 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\Connection; - -use Google\ApiCore\Call; -use Google\ApiCore\CredentialsWrapper; -use Google\ApiCore\OperationResponse; -use Google\ApiCore\Serializer; -use Google\ApiCore\Testing\MockResponse; -use Google\ApiCore\Transport\TransportInterface; -use Google\Cloud\Core\GrpcRequestWrapper; -use Google\Cloud\Core\GrpcTrait; -use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Spanner\Admin\Database\V1\Backup; -use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupEncryptionConfig; -use Google\Cloud\Spanner\Admin\Database\V1\EncryptionConfig; -use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig; -use Google\Cloud\Spanner\Admin\Instance\V1\Instance; -use Google\Cloud\Spanner\Admin\Instance\V1\Instance\State; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; -use Google\Cloud\Spanner\Connection\Grpc; -use Google\Cloud\Spanner\V1\BatchWriteRequest\MutationGroup as MutationGroupProto; -use Google\Cloud\Spanner\V1\DeleteSessionRequest; -use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest\Statement; -use Google\Cloud\Spanner\V1\ExecuteSqlRequest\QueryOptions; -use Google\Cloud\Spanner\V1\KeySet; -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\PartialResultSet; -use Google\Cloud\Spanner\V1\PartitionOptions; -use Google\Cloud\Spanner\V1\RequestOptions; -use Google\Cloud\Spanner\V1\Session; -use Google\Cloud\Spanner\V1\SpannerClient; -use Google\Cloud\Spanner\V1\TransactionOptions; -use Google\Cloud\Spanner\V1\TransactionOptions\PartitionedDml; -use Google\Cloud\Spanner\V1\TransactionOptions\PBReadOnly; -use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite; -use Google\Cloud\Spanner\V1\TransactionSelector; -use Google\Cloud\Spanner\V1\Type; -use Google\Cloud\Spanner\ValueMapper; -use Google\Cloud\Spanner\MutationGroup; -use Google\Protobuf\FieldMask; -use Google\Protobuf\ListValue; -use Google\Protobuf\NullValue; -use Google\Protobuf\Struct; -use Google\Protobuf\Timestamp; -use Google\Protobuf\Value; -use GuzzleHttp\Promise\PromiseInterface; -use http\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; - -/** - * @group spanner - * @group spanner-grpc - */ -class GrpcTest extends TestCase -{ - use GrpcTestTrait; - use GrpcTrait; - use ProphecyTrait; - - const CONFIG = 'projects/my-project/instanceConfigs/config-1'; - const DATABASE = 'projects/my-project/instances/instance-1/databases/database-1'; - const INSTANCE = 'projects/my-project/instances/instance-1'; - const PROJECT = 'projects/my-project'; - const SESSION = 'projects/my-project/instances/instance-1/databases/database-1/sessions/session-1'; - const TABLE = 'table-1'; - const TRANSACTION = 'transaction-1'; - - private $requestWrapper; - private $serializer; - private $successMessage; - private $lro; - - public function setUp(): void - { - $this->checkAndSkipGrpcTests(); - - $this->requestWrapper = $this->prophesize(GrpcRequestWrapper::class); - $this->serializer = new Serializer; - $this->successMessage = 'success'; - $this->lro = $this->prophesize(OperationResponse::class)->reveal(); - } - - public function testApiEndpoint() - { - $expected = 'foobar.com'; - - $grpc = new GrpcStub(['apiEndpoint' => $expected]); - - $this->assertEquals($expected, $grpc->config['apiEndpoint']); - } - - public function testListInstanceConfigs() - { - $this->assertCallCorrect('listInstanceConfigs', [ - 'projectName' => self::PROJECT - ], $this->expectResourceHeader(self::PROJECT, [ - self::PROJECT - ])); - } - - public function testGetInstanceConfig() - { - $this->assertCallCorrect('getInstanceConfig', [ - 'name' => self::CONFIG, - 'projectName' => self::PROJECT - ], $this->expectResourceHeader(self::PROJECT, [ - self::CONFIG - ])); - } - - public function testCreateInstanceConfig() - { - list ($args, $config) = $this->instanceConfig(); - - $this->assertCallCorrect( - 'createInstanceConfig', - [ - 'projectName' => self::PROJECT, - 'instanceConfigId' => self::CONFIG - ] + $args, - $this->expectResourceHeader(self::CONFIG, [ - self::PROJECT, - self::CONFIG, - $config - ]), - $this->lro, - null - ); - } - - public function testUpdateInstanceConfig() - { - list ($args, $config, $fieldMask) = $this->instanceConfig(false); - $this->assertCallCorrect('updateInstanceConfig', $args, $this->expectResourceHeader(self::CONFIG, [ - $config, $fieldMask - ]), $this->lro, null); - } - - public function testDeleteInstanceConfig() - { - $this->assertCallCorrect('deleteInstanceConfig', [ - 'name' => self::CONFIG - ], $this->expectResourceHeader(self::CONFIG, [ - self::CONFIG - ])); - } - - public function testListInstances() - { - $this->assertCallCorrect('listInstances', [ - 'projectName' => self::PROJECT - ], $this->expectResourceHeader(self::PROJECT, [ - self::PROJECT - ])); - } - - public function testGetInstance() - { - $this->assertCallCorrect('getInstance', [ - 'name' => self::INSTANCE, - 'projectName' => self::PROJECT - ], $this->expectResourceHeader(self::PROJECT, [ - self::INSTANCE - ])); - } - - public function testGetInstanceWithFieldMaskArray() - { - $fieldNames = ['name', 'displayName', 'nodeCount']; - - $mask = []; - foreach (array_values($fieldNames) as $key) { - $mask[] = Serializer::toSnakeCase($key); - } - - $fieldMask = $this->serializer->decodeMessage(new FieldMask, ['paths' => $mask]); - $this->assertCallCorrect('getInstance', [ - 'name' => self::INSTANCE, - 'projectName' => self::PROJECT, - 'fieldMask' => $fieldNames - ], $this->expectResourceHeader(self::PROJECT, [ - self::INSTANCE, - ['fieldMask' => $fieldMask] - ])); - } - - public function testGetInstanceWithFieldMaskString() - { - $fieldNames = 'nodeCount'; - $mask[] = Serializer::toSnakeCase($fieldNames); - - $fieldMask = $this->serializer->decodeMessage(new FieldMask, ['paths' => $mask]); - $this->assertCallCorrect('getInstance', [ - 'name' => self::INSTANCE, - 'projectName' => self::PROJECT, - 'fieldMask' => $fieldNames - ], $this->expectResourceHeader(self::PROJECT, [ - self::INSTANCE, - ['fieldMask' => $fieldMask] - ])); - } - - public function testCreateInstance() - { - list ($args, $instance) = $this->instance(); - - $this->assertCallCorrect('createInstance', [ - 'projectName' => self::PROJECT, - 'instanceId' => self::INSTANCE - ] + $args, $this->expectResourceHeader(self::INSTANCE, [ - self::PROJECT, - self::INSTANCE, - $instance - ]), $this->lro, null); - } - - public function testCreateInstanceWithProcessingNodes() - { - list ($args, $instance) = $this->instance(true, false); - - $this->assertCallCorrect('createInstance', [ - 'projectName' => self::PROJECT, - 'instanceId' => self::INSTANCE, - 'processingUnits' => 1000 - ] + $args, $this->expectResourceHeader(self::INSTANCE, [ - self::PROJECT, - self::INSTANCE, - $instance - ]), $this->lro, null); - } - - public function testUpdateInstance() - { - list ($args, $instance, $fieldMask) = $this->instance(false); - - $this->assertCallCorrect('updateInstance', $args, $this->expectResourceHeader(self::INSTANCE, [ - $instance, $fieldMask - ]), $this->lro, null); - } - - public function testDeleteInstance() - { - $this->assertCallCorrect('deleteInstance', [ - 'name' => self::INSTANCE - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE - ])); - } - - public function testSetInstanceIamPolicy() - { - $policy = ['foo' => 'bar']; - - $this->assertCallCorrect('setInstanceIamPolicy', [ - 'resource' => self::INSTANCE, - 'policy' => $policy - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE, - $policy - ], false)); - } - - public function testGetInstanceIamPolicy() - { - $this->assertCallCorrect('getInstanceIamPolicy', [ - 'resource' => self::INSTANCE - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE - ])); - } - - public function testTestInstanceIamPermissions() - { - $permissions = ['permission1', 'permission2']; - $this->assertCallCorrect('testInstanceIamPermissions', [ - 'resource' => self::INSTANCE, - 'permissions' => $permissions - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE, - $permissions - ], false)); - } - - public function testListDatabases() - { - $this->assertCallCorrect('listDatabases', [ - 'instance' => self::INSTANCE - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE - ])); - } - - public function testCreateDatabase() - { - $createStmt = 'CREATE Foo'; - $extraStmts = [ - 'CREATE TABLE Bar' - ]; - $encryptionConfig = ['kmsKeyName' => 'kmsKeyName']; - $expectedEncryptionConfig = $this->serializer->decodeMessage(new EncryptionConfig, $encryptionConfig); - - $this->assertCallCorrect('createDatabase', [ - 'instance' => self::INSTANCE, - 'createStatement' => $createStmt, - 'extraStatements' => $extraStmts, - 'encryptionConfig' => $encryptionConfig - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE, - $createStmt, - [ - 'extraStatements' => $extraStmts, - 'encryptionConfig' => $expectedEncryptionConfig - ] - ]), $this->lro, null); - } - - public function testCreateBackup() - { - $backupId = "backup-id"; - $expireTime = new \DateTime("+ 7 hours"); - $backup = [ - 'database' => self::DATABASE, - 'expireTime' => $expireTime->format('Y-m-d\TH:i:s.u\Z') - ]; - $expectedBackup = $this->serializer->decodeMessage(new Backup(), [ - 'expireTime' => $this->formatTimestampForApi($backup['expireTime']) - ] + $backup); - - $encryptionConfig = [ - 'kmsKeyName' => 'kmsKeyName', - 'encryptionType' => CreateBackupEncryptionConfig\EncryptionType::CUSTOMER_MANAGED_ENCRYPTION - ]; - $expectedEncryptionConfig = $this->serializer->decodeMessage( - new CreateBackupEncryptionConfig, - $encryptionConfig - ); - - $this->assertCallCorrect('createBackup', [ - 'instance' => self::INSTANCE, - 'backupId' => $backupId, - 'backup' => $backup, - 'encryptionConfig' => $encryptionConfig - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE, - $backupId, - $expectedBackup, - [ - 'encryptionConfig' => $expectedEncryptionConfig - ] - ]), $this->lro, null); - } - - public function testRestoreDatabase() - { - $databaseId = 'test-database'; - $encryptionConfig = [ - 'kmsKeyName' => 'kmsKeyName', - 'encryptionType' => RestoreDatabaseEncryptionConfig\EncryptionType::CUSTOMER_MANAGED_ENCRYPTION - ]; - $expectedEncryptionConfig = $this->serializer->decodeMessage( - new RestoreDatabaseEncryptionConfig, - $encryptionConfig - ); - - $this->assertCallCorrect('restoreDatabase', [ - 'instance' => self::INSTANCE, - 'databaseId' => $databaseId, - 'encryptionConfig' => $encryptionConfig - ], $this->expectResourceHeader(self::INSTANCE, [ - self::INSTANCE, - $databaseId, - [ - 'encryptionConfig' => $expectedEncryptionConfig - ] - ]), $this->lro, null); - } - - public function testUpdateDatabaseDdl() - { - $statements = [ - 'CREATE TABLE Bar' - ]; - - $this->assertCallCorrect('updateDatabaseDdl', [ - 'name' => self::DATABASE, - 'statements' => $statements - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE, - $statements - ], false), $this->lro, null); - } - - public function testDropDatabase() - { - $this->assertCallCorrect('dropDatabase', [ - 'name' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE - ])); - } - - public function testGetDatabase() - { - $this->assertCallCorrect('getDatabase', [ - 'name' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE - ])); - } - - public function testGetDatabaseDdl() - { - $this->assertCallCorrect('getDatabaseDdl', [ - 'name' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE - ])); - } - - public function testSetDatabaseIamPolicy() - { - $policy = ['foo' => 'bar']; - - $this->assertCallCorrect('setDatabaseIamPolicy', [ - 'resource' => self::DATABASE, - 'policy' => $policy - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE, - $policy - ], false)); - } - - public function testGetDatabaseIamPolicy() - { - $this->assertCallCorrect('getDatabaseIamPolicy', [ - 'resource' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE - ])); - } - - public function testTestDatabaseIamPermissions() - { - $permissions = ['permission1', 'permission2']; - $this->assertCallCorrect('testDatabaseIamPermissions', [ - 'resource' => self::DATABASE, - 'permissions' => $permissions - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE, - $permissions - ], false)); - } - - /** - * @dataProvider larOptions - */ - public function testCreateSession($larEnabled, $grpcConfig) - { - $labels = ['foo' => 'bar']; - - $this->assertCallCorrect('createSession', [ - 'database' => self::DATABASE, - 'session' => [ - 'labels' => $labels - ] - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE, - [ - 'session' => (new Session)->setLabels($labels) - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testCreateSessionAsync() - { - $promise = $this->prophesize(PromiseInterface::class)->reveal(); - $client = $this->prophesize(SpannerClient::class); - $transport = $this->prophesize(TransportInterface::class); - $transport->startUnaryCall( - Argument::type(Call::class), - Argument::withEntry('headers', [ - 'x-goog-spanner-route-to-leader' => ['true'], - 'google-cloud-resource-prefix' => ['database1'] - ]) - )->willReturn($promise); - - $client->getTransport()->willReturn($transport->reveal()); - - $grpc = new Grpc(['gapicSpannerClient' => $client->reveal()]); - - $promise = $grpc->createSessionAsync([ - 'database' => 'database1', - 'session' => [ - 'labels' => [ 'foo' => 'bar' ] - ] - ]); - - $this->assertInstanceOf(PromiseInterface::class, $promise); - } - - /** - * @dataProvider larOptions - */ - public function testBatchCreateSessions($larEnabled, $grpcConfig) - { - $count = 10; - $template = [ - 'labels' => [ - 'foo' => 'bar' - ] - ]; - - $this->assertCallCorrect('batchCreateSessions', [ - 'database' => self::DATABASE, - 'sessionCount' => $count, - 'sessionTemplate' => $template - ], $this->expectResourceHeader(self::DATABASE, [ - self::DATABASE, $count, [ - 'sessionTemplate' => $this->serializer->decodeMessage(new Session, $template) - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testBatchWrite() - { - $mutationGroups = [ - (new MutationGroup(false)) - ->insertOrUpdate( - "Singers", - ['SingerId' => 16, 'FirstName' => 'Scarlet', 'LastName' => 'Terry'] - )->toArray(), - (new MutationGroup(false)) - ->insertOrUpdate( - "Singers", - ['SingerId' => 17, 'FirstName' => 'Marc', 'LastName' => 'Kristen'] - )->insertOrUpdate( - "Albums", - ['AlbumId' => 1, 'SingerId' => 17, 'AlbumTitle' => 'Total Junk'] - )->toArray() - ]; - - $expectedMutationGroups = [ - new MutationGroupProto(['mutations' => [ - new Mutation(['insert_or_update' => new Write([ - 'table' => 'Singers', - 'columns' => ['SingerId', 'FirstName', 'LastName'], - 'values' => [new ListValue(['values' => [ - new Value(['string_value' => '16']), - new Value(['string_value' => 'Scarlet']), - new Value(['string_value' => 'Terry']) - ]])] - ])]) - ]]), - new MutationGroupProto(['mutations' => [ - new Mutation(['insert_or_update' => new Write([ - 'table' => 'Singers', - 'columns' => ['SingerId', 'FirstName', 'LastName'], - 'values' => [new ListValue(['values' => [ - new Value(['string_value' => '17']), - new Value(['string_value' => 'Marc']), - new Value(['string_value' => 'Kristen']) - ]])] - ])]), - new Mutation(['insert_or_update' => new Write([ - 'table' => 'Albums', - 'columns' => ['AlbumId', 'SingerId', 'AlbumTitle'], - 'values' => [new ListValue(['values' => [ - new Value(['string_value' => '1']), - new Value(['string_value' => '17']), - new Value(['string_value' => 'Total Junk']) - ]])] - ])]), - ]]), - ]; - - $this->assertCallCorrect( - 'batchWrite', - [ - 'database' => self::DATABASE, - 'session' => self::SESSION, - 'mutationGroups' => $mutationGroups, - ], - $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $expectedMutationGroups, - [] - ]), - ); - } - - /** - * @dataProvider larOptions - */ - public function testGetSession($larEnabled, $grpcConfig) - { - $this->assertCallCorrect('getSession', [ - 'database' => self::DATABASE, - 'name' => self::SESSION - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testDeleteSession() - { - $this->assertCallCorrect('deleteSession', [ - 'database' => self::DATABASE, - 'name' => self::SESSION - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION - ])); - } - - public function testDeleteSessionAsync() - { - $promise = $this->prophesize(PromiseInterface::class) - ->reveal(); - $sessionName = 'session1'; - $databaseName = 'database1'; - $request = new DeleteSessionRequest(); - $request->setName($sessionName); - $client = $this->prophesize(SpannerClient::class); - $transport = $this->prophesize(TransportInterface::class); - $transport->startUnaryCall( - Argument::type(Call::class), - Argument::type('array') - )->willReturn($promise); - $client->getTransport() - ->willReturn($transport->reveal()); - $grpc = new Grpc(['gapicSpannerClient' => $client->reveal()]); - $call = $grpc->deleteSessionAsync([ - 'name' => $sessionName, - 'database' => $databaseName - ]); - - $this->assertInstanceOf(PromiseInterface::class, $call); - } - - /** - * @dataProvider larOptions - */ - public function testExecuteStreamingSql($larEnabled, $grpcConfig) - { - $sql = 'SELECT 1'; - - $mapper = new ValueMapper(false); - $mapped = $mapper->formatParamsForExecuteSql(['foo' => 'bar']); - - $expectedParams = $this->serializer->decodeMessage( - new Struct, - $this->formatStructForApi($mapped['params']) - ); - - $expectedParamTypes = $mapped['paramTypes']; - foreach ($expectedParamTypes as $key => $param) { - $expectedParamTypes[$key] = $this->serializer->decodeMessage(new Type, $param); - } - - $this->assertCallCorrect('executeStreamingSql', [ - 'session' => self::SESSION, - 'sql' => $sql, - 'transactionId' => self::TRANSACTION, - 'database' => self::DATABASE, - 'headers' => ['x-goog-spanner-route-to-leader' => ['true']] - ] + $mapped, $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $sql, - [ - 'transaction' => $this->transactionSelector(), - 'params' => $expectedParams, - 'paramTypes' => $expectedParamTypes - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testExecuteStreamingSqlWithRequestOptions() - { - $sql = 'SELECT 1'; - $requestOptions = ["priority" => RequestOptions\Priority::PRIORITY_LOW]; - $expectedRequestOptions = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - - $this->assertCallCorrect('executeStreamingSql', [ - 'session' => self::SESSION, - 'sql' => $sql, - 'transactionId' => self::TRANSACTION, - 'database' => self::DATABASE, - 'params' => [], - 'requestOptions' => $requestOptions - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $sql, - [ - 'transaction' => $this->transactionSelector(), - 'requestOptions' => $expectedRequestOptions - ] - ])); - } - - /** - * @dataProvider queryOptions - */ - public function testExecuteStreamingSqlWithQueryOptions( - array $methodOptions, - array $envOptions, - array $clientOptions, - array $expectedOptions - ) { - $sql = 'SELECT 1'; - - if (array_key_exists('optimizerVersion', $envOptions)) { - putenv('SPANNER_OPTIMIZER_VERSION=' . $envOptions['optimizerVersion']); - } - if (array_key_exists('optimizerStatisticsPackage', $envOptions)) { - putenv('SPANNER_OPTIMIZER_STATISTICS_PACKAGE=' . $envOptions['optimizerStatisticsPackage']); - } - $gapic = $this->prophesize(SpannerClient::class); - $gapic->executeStreamingSql( - self::SESSION, - $sql, - Argument::that(function ($arguments) use ($expectedOptions) { - $queryOptions = $arguments['queryOptions'] ?? null; - $expectedOptions += ['optimizerVersion' => null, 'optimizerStatisticsPackage' => null]; - $this->assertEquals( - $queryOptions ? $queryOptions->getOptimizerVersion() : null, - $expectedOptions['optimizerVersion'] - ); - $this->assertEquals( - $queryOptions ? $queryOptions->getOptimizerStatisticsPackage() : null, - $expectedOptions['optimizerStatisticsPackage'] - ); - return true; - }) - )->shouldBeCalledOnce(); - - $grpc = new Grpc([ - 'gapicSpannerClient' => $gapic->reveal() - ] + ['queryOptions' => $clientOptions]); - - $grpc->executeStreamingSql([ - 'database' => self::DATABASE, - 'session' => self::SESSION, - 'sql' => $sql, - 'params' => [] - ] + ['queryOptions' => $methodOptions]); - - if ($envOptions) { - putenv('SPANNER_OPTIMIZER_VERSION='); - putenv('SPANNER_OPTIMIZER_STATISTICS_PACKAGE='); - } - } - - public function queryOptions() - { - return [ - [ - ['optimizerVersion' => '8'], - [ - 'optimizerVersion' => '7', - 'optimizerStatisticsPackage' => "auto_20191128_18_47_22UTC", - ], - ['optimizerStatisticsPackage' => "auto_20191128_14_47_22UTC"], - [ - 'optimizerVersion' => '8', - 'optimizerStatisticsPackage' => "auto_20191128_18_47_22UTC", - ] - ], - [ - [], - ['optimizerVersion' => '7'], - [ - 'optimizerVersion' => '6', - 'optimizerStatisticsPackage' => "auto_20191128_14_47_22UTC", - ], - [ - 'optimizerVersion' => '7', - 'optimizerStatisticsPackage' => "auto_20191128_14_47_22UTC", - ] - ], - [ - ['optimizerStatisticsPackage' => "auto_20191128_23_47_22UTC"], - [], - [ - 'optimizerVersion' => '6', - 'optimizerStatisticsPackage' => "auto_20191128_14_47_22UTC", - ], - [ - 'optimizerVersion' => '6', - 'optimizerStatisticsPackage' => "auto_20191128_23_47_22UTC", - ] - ], - [ - [], - [], - [], - [] - ] - ]; - } - - /** - * @dataProvider readKeysets - */ - public function testStreamingRead($keyArg, $keyObj, $larEnabled, $grpcConfig) - { - $columns = [ - 'id', - 'name' - ]; - - $this->assertCallCorrect('streamingRead', [ - 'keySet' => $keyArg, - 'transactionId' => self::TRANSACTION, - 'session' => self::SESSION, - 'table' => self::TABLE, - 'columns' => $columns, - 'database' => self::DATABASE, - 'headers' => ['x-goog-spanner-route-to-leader' => ['true']] - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - self::TABLE, - $columns, - $keyObj, - [ - 'transaction' => $this->transactionSelector() - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testStreamingReadWithRequestOptions() - { - $columns = [ - 'id', - 'name' - ]; - $requestOptions = ['priority' => RequestOptions\Priority::PRIORITY_LOW]; - $expectedRequestOptions = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - - $this->assertCallCorrect('streamingRead', [ - 'keySet' => [], - 'transactionId' => self::TRANSACTION, - 'session' => self::SESSION, - 'table' => self::TABLE, - 'columns' => $columns, - 'database' => self::DATABASE, - 'requestOptions' => $requestOptions - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - self::TABLE, - $columns, - new KeySet, - [ - 'transaction' => $this->transactionSelector(), - 'requestOptions' => $expectedRequestOptions - ] - ])); - } - - public function readKeysets() - { - $this->setUp(); - - return [ - [ - [], - new KeySet, - true, - ['routeToLeader' => true] - ], [ - ['keys' => [1]], - $this->serializer->decodeMessage(new KeySet, [ - 'keys' => [ - [ - 'values' => [ - [ - 'number_value' => 1 - ] - ] - ] - ] - ]), - false, - ['routeToLeader' => false] - ], [ - ['keys' => [[1,1]]], - $this->serializer->decodeMessage(new KeySet, [ - 'keys' => [ - [ - 'values' => [ - [ - 'number_value' => 1 - ], - [ - 'number_value' => 1 - ] - ] - ] - ] - ]), - false, - ['routeToLeader' => false] - ] - ]; - } - - /** - * @dataProvider larOptions - */ - public function testExecuteBatchDml($larEnabled, $grpcConfig) - { - $statements = [ - [ - 'sql' => 'SELECT 1', - 'params' => [] - ] - ]; - - $statementsObjs = [ - new Statement([ - 'sql' => 'SELECT 1' - ]) - ]; - - $this->assertCallCorrect('executeBatchDml', [ - 'session' => self::SESSION, - 'database' => self::DATABASE, - 'transactionId' => self::TRANSACTION, - 'statements' => $statements, - 'seqno' => 1 - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $this->transactionSelector(), - $statementsObjs, - 1 - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function testExecuteBatchDmlWithRequestOptions() - { - $statements = [ - [ - 'sql' => 'SELECT 1', - 'params' => [] - ] - ]; - - $statementsObjs = [ - new Statement([ - 'sql' => 'SELECT 1' - ]) - ]; - $requestOptions = ['priority' => RequestOptions\Priority::PRIORITY_LOW]; - $expectedRequestOptions = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - - - $this->assertCallCorrect('executeBatchDml', [ - 'session' => self::SESSION, - 'database' => self::DATABASE, - 'transactionId' => self::TRANSACTION, - 'statements' => $statements, - 'seqno' => 1, - 'requestOptions' => $requestOptions - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $this->transactionSelector(), - $statementsObjs, - 1, - ['requestOptions' => $expectedRequestOptions] - ], true, true)); - } - - /** - * @dataProvider transactionTypes - */ - public function testBeginTransaction($optionsArr, $optionsObj, $larEnabled, $grpcConfig) - { - $this->assertCallCorrect('beginTransaction', [ - 'session' => self::SESSION, - 'transactionOptions' => $optionsArr, - 'database' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $optionsObj - ], true, $larEnabled, $optionsArr), null, '', $grpcConfig); - } - - public function transactionTypes() - { - $ts = (new \DateTime)->format('Y-m-d\TH:i:s.u\Z'); - $pbTs = new Timestamp($this->formatTimestampForApi($ts)); - $readOnlyClass = PHP_VERSION_ID >= 80100 - ? PBReadOnly::class - : 'Google\Cloud\Spanner\V1\TransactionOptions\ReadOnly'; - - return [ - [ - ['readWrite' => []], - new TransactionOptions([ - 'read_write' => new ReadWrite - ]), - true, - ['routeToLeader' => true] - ], [ - [ - 'readOnly' => [ - 'minReadTimestamp' => $ts, - 'readTimestamp' => $ts - ] - ], - new TransactionOptions([ - 'read_only' => new $readOnlyClass([ - 'min_read_timestamp' => $pbTs, - 'read_timestamp' => $pbTs - ]) - ]), - true, - ['routeToLeader' => true] - ], [ - ['partitionedDml' => []], - new TransactionOptions([ - 'partitioned_dml' => new PartitionedDml - ]), - true, - ['routeToLeader' => true] - ] - ]; - } - - /** - * @dataProvider commit - */ - public function testCommit($mutationsArr, $mutationsObjArr, $larEnabled, $grpcConfig) - { - $this->assertCallCorrect('commit', [ - 'session' => self::SESSION, - 'mutations' => $mutationsArr, - 'singleUseTransaction' => true, - 'database' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $mutationsObjArr, - [ - 'singleUseTransaction' => new TransactionOptions([ - 'read_write' => new ReadWrite - ]) - ] - ], true, $grpcConfig), null, '', $grpcConfig); - } - - /** - * @dataProvider commit - */ - public function testCommitWithRequestOptions($mutationsArr, $mutationsObjArr) - { - $requestOptions = ['priority' => RequestOptions\Priority::PRIORITY_LOW]; - $expectedRequestOptions = $this->serializer->decodeMessage( - new RequestOptions, - $requestOptions - ); - $this->assertCallCorrect('commit', [ - 'session' => self::SESSION, - 'mutations' => $mutationsArr, - 'singleUseTransaction' => true, - 'database' => self::DATABASE, - 'requestOptions' => $requestOptions - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $mutationsObjArr, - [ - 'singleUseTransaction' => new TransactionOptions([ - 'read_write' => new ReadWrite - ]), - 'requestOptions' => $expectedRequestOptions - ] - ], true, true)); - } - - public function commit() - { - $mutation = [ - 'table' => self::TABLE, - 'columns' => [ - 'col1' - ], - 'values' => [ - 'val1' - ] - ]; - - $write = new Write([ - 'table' => self::TABLE, - 'columns' => ['col1'], - 'values' => [ - new ListValue([ - 'values' => [ - new Value([ - 'string_value' => 'val1' - ]) - ] - ]) - ] - ]); - - return [ - [ - [], [], true, ['routeToLeader' => true] - ], [ - [ - [ - 'delete' => [ - 'table' => self::TABLE, - 'keySet' => [] - ] - ] - ], - [ - new Mutation([ - 'delete' => new Delete([ - 'table' => self::TABLE, - 'key_set' => new KeySet - ]) - ]) - ], - true, - ['routeToLeader' => true] - ], [ - [ - [ - 'insert' => $mutation - ] - ], - [ - new Mutation([ - 'insert' => $write - ]) - ], - true, - ['routeToLeader' => true] - ], [ - [ - [ - 'update' => $mutation - ] - ], - [ - new Mutation([ - 'update' => $write - ]) - ], - true, - ['routeToLeader' => true] - ], [ - [ - [ - 'insertOrUpdate' => $mutation - ] - ], - [ - new Mutation([ - 'insert_or_update' => $write - ]) - ], - true, - ['routeToLeader' => true] - ], [ - [ - [ - 'replace' => $mutation - ] - ], - [ - new Mutation([ - 'replace' => $write - ]) - ], - true, - ['routeToLeader' => true] - ] - ]; - } - - /** - * @dataProvider larOptions - */ - public function testRollback($larEnabled, $grpcConfig) - { - $this->assertCallCorrect('rollback', [ - 'session' => self::SESSION, - 'transactionId' => self::TRANSACTION, - 'database' => self::DATABASE - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - self::TRANSACTION - ], true, $larEnabled), null, '', $grpcConfig); - } - - /** - * @dataProvider partitionOptions - */ - public function testPartitionQuery($partitionOptions, $partitionOptionsObj, $larEnabled, $grpcConfig) - { - $sql = 'SELECT 1'; - $this->assertCallCorrect('partitionQuery', [ - 'session' => self::SESSION, - 'sql' => $sql, - 'params' => [], - 'transactionId' => self::TRANSACTION, - 'database' => self::DATABASE, - 'partitionOptions' => $partitionOptions, - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - $sql, - [ - 'transaction' => $this->transactionSelector(), - 'partitionOptions' => $partitionOptionsObj - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - /** - * @dataProvider partitionOptions - */ - public function testPartitionRead($partitionOptions, $partitionOptionsObj, $larEnabled, $grpcConfig) - { - $this->assertCallCorrect('partitionRead', [ - 'session' => self::SESSION, - 'keySet' => [], - 'table' => self::TABLE, - 'transactionId' => self::TRANSACTION, - 'database' => self::DATABASE, - 'partitionOptions' => $partitionOptions, - ], $this->expectResourceHeader(self::DATABASE, [ - self::SESSION, - self::TABLE, - new KeySet, - [ - 'transaction' => $this->transactionSelector(), - 'partitionOptions' => $partitionOptionsObj - ] - ], true, $larEnabled), null, '', $grpcConfig); - } - - public function partitionOptions() - { - return [ - [ - [], - new PartitionOptions, - true, - ['routeToLeader' => true] - ], - [ - ['maxPartitions' => 10], - new PartitionOptions([ - 'max_partitions' => 10 - ]), - true, - ['routeToLeader' => true] - ] - ]; - } - - /** - * @dataProvider keysets - */ - public function testFormatKeySet($input, $expected) - { - $this->assertEquals( - $expected, - $this->callPrivateMethod('formatKeySet', [$input]) - ); - } - - public function keysets() - { - return [ - [ - [], - [] - ], [ - [ - 'keys' => [ - [ - 1, - 2 - ] - ] - ], - [ - 'keys' => [ - $this->formatListForApi([1, 2]) - ] - ] - ], [ - [ - 'ranges' => [ - [ - 'startOpen' => [1], - 'endClosed' => [2] - ] - ], - ], [ - 'ranges' => [ - [ - 'startOpen' => $this->formatListForApi([1]), - 'endClosed' => $this->formatListForApi([2]), - ] - ] - ] - ], [ - [ - 'ranges' => [] - ], - [] - ] - ]; - } - - /** - * @dataProvider fieldvalues - */ - public function testFieldValue($input, $expected) - { - $this->assertEquals( - $expected, - $this->callPrivateMethod('fieldValue', [$input]) - ); - } - - public function fieldvalues() - { - return [ - [ - 'foo', - new Value([ - 'string_value' => 'foo' - ]) - ], [ - 1, - new Value([ - 'number_value' => 1 - ]) - ], [ - false, - new Value([ - 'bool_value' => false - ]) - ], [ - null, - new Value([ - 'null_value' => NullValue::NULL_VALUE - ]) - ], [ - [ - 'a' => 'b' - ], - new Value([ - 'struct_value' => new Struct([ - 'fields' => [ - 'a' => new Value([ - 'string_value' => 'b' - ]) - ] - ]) - ]) - ], [ - [ - 'a', 'b', 'c' - ], - new Value([ - 'list_value' => new ListValue([ - 'values' => [ - new Value([ - 'string_value' => 'a' - ]), - new Value([ - 'string_value' => 'b' - ]), - new Value([ - 'string_value' => 'c' - ]), - ] - ]) - ]) - ] - ]; - } - - /** - * @dataProvider transactionOptions - */ - public function testTransactionOptions($input, $expected) - { - // Since the tested method uses pass-by-reference arg, the callPrivateMethod function won't work. - // test on php7 only is better than nothing. - if (version_compare(PHP_VERSION, '7.0.0', '<')) { - $this->markTestSkipped('only works in php 7.'); - return; - } - - $grpc = new Grpc; - $createTransactionSelector = function () { - $args = func_get_args(); - return $this->createTransactionSelector($args[0]); - }; - - $this->assertEquals( - $expected->serializeToJsonString(), - $createTransactionSelector->call($grpc, $input)->serializeToJsonString() - ); - } - - public function transactionOptions() - { - return [ - [ - [ - 'transactionId' => self::TRANSACTION - ], - $this->transactionSelector() - ], [ - [ - 'transaction' => [ - 'singleUse' => [ - 'readWrite' => [] - ] - ] - ], - new TransactionSelector([ - 'single_use' => new TransactionOptions([ - 'read_write' => new ReadWrite - ]) - ]) - ], [ - [ - 'transaction' => [ - 'begin' => [ - 'readWrite' => [] - ] - ] - ], - new TransactionSelector([ - 'begin' => new TransactionOptions([ - 'read_write' => new ReadWrite - ]) - ]) - ] - ]; - } - - public function larOptions() - { - return [ - [ - true, - ['routeToLeader' => true] - ], [ - false, - ['routeToLeader' => false] - ] - ]; - } - - public function testPartialResultSetCustomEncoder() - { - $partialResultSet = new PartialResultSet(); - $partialResultSet->mergeFromJsonString(json_encode([ - 'metadata' => [ - 'transaction' => [ - 'id' => base64_encode(0b00010100) // bytedata is represented as a base64-encoded string in JSON - ], - 'rowType' => [ - 'fields' => [ - ['type' => ['code' => 'INT64']] // enums are represented as their string equivalents in JSON - ] - ], - ], - ])); - - $this->assertEquals(0b00010100, $partialResultSet->getMetadata()->getTransaction()->getId()); - $this->assertEquals(2, $partialResultSet->getMetadata()->getRowType()->getFields()[0]->getType()->getCode()); - - // decode the message and ensure it's decoded as expected - $grpc = new Grpc(); - $serializerProp = new \ReflectionProperty($grpc, 'serializer'); - $serializerProp->setAccessible(true); - $serializer = $serializerProp->getValue($grpc); - $arr = $serializer->encodeMessage($partialResultSet); - - // We expect this to be the binary string - $this->assertEquals(0b00010100, $arr['metadata']['transaction']['id']); - // We expect this to be the integer - $this->assertEquals(2, $arr['metadata']['rowType']['fields'][0]['type']['code']); - } - private function assertCallCorrect( - $method, - array $args, - array $expectedArgs, - $return = null, - $result = '', - $grpcConfig = [] - ) { - $this->requestWrapper->send( - Argument::type('callable'), - $expectedArgs, - Argument::type('array') - )->shouldBeCalled()->willReturn($return ?: $this->successMessage); - - $connection = new Grpc($grpcConfig); - $connection->setRequestWrapper($this->requestWrapper->reveal()); - - $this->assertEquals($result !== '' ? $result : $this->successMessage, $connection->$method($args)); - } - - /** - * Add the resource header to the args list. - * - * @param string $val The header value to add. - * @param array $args The remaining call args. - * @param boolean $append If true, should the last value in $args be an - * array, the header will be appended to that array. If false, the - * header will be added to a separate array. - * @param boolean $lar If true, will add the x-goog-spanner-route-to-leader - * header. - * @param array $options The options to add to the call. - * @return array - */ - private function expectResourceHeader( - $val, - array $args, - $append = true, - $lar = false, - $options = [] - ) { - $header = [ - 'google-cloud-resource-prefix' => [$val] - ]; - if ($lar && !isset($options['readOnly'])) { - $header['x-goog-spanner-route-to-leader'] = ['true']; - } - - $end = end($args); - if (!is_array($end) || !$append) { - $args[]['headers'] = $header; - } elseif (is_array($end)) { - $keys = array_keys($args); - $key = end($keys); - $args[$key]['headers'] = $header; - } - return $args; - } - - private function callPrivateMethod($method, array $args) - { - $grpc = new Grpc; - $ref = new \ReflectionClass($grpc); - - $method = $ref->getMethod($method); - $method->setAccessible(true); - - array_unshift($args, $grpc); - return call_user_func_array([$method, 'invoke'], $args); - } - - private function instanceConfig($full = true) - { - $args = [ - 'name' => self::CONFIG, - 'displayName' => self::CONFIG, - ]; - - if ($full) { - $args = array_merge($args, [ - 'baseConfig' => self::CONFIG, - 'configType' => InstanceConfig\Type::TYPE_UNSPECIFIED, - 'state' => State::CREATING, - 'labels' => [], - 'replicas' => [], - 'optionalReplicas' => [], - 'leaderOptions' => [], - 'reconciling' => false, - ]); - } - - $mask = []; - foreach (array_keys($args) as $key) { - if ($key != "name") { - $mask[] = Serializer::toSnakeCase($key); - } - } - - $fieldMask = $this->serializer->decodeMessage(new FieldMask, ['paths' => $mask]); - - return [ - $args, - $this->serializer->decodeMessage(new InstanceConfig, $args), - $fieldMask - ]; - } - - private function instance($full = true, $nodes = true) - { - $args = [ - 'name' => self::INSTANCE, - 'displayName' => self::INSTANCE, - ]; - - if ($full) { - if ($nodes) { - $args = array_merge($args, [ - 'config' => self::CONFIG, - 'nodeCount' => 1, - 'state' => State::CREATING, - 'labels' => [] - ]); - } else { - $args = array_merge($args, [ - 'config' => self::CONFIG, - 'processingUnits' => 1000, - 'state' => State::CREATING, - 'labels' => [] - ]); - } - } - - $mask = []; - foreach (array_keys($args) as $key) { - if ($key != "name") { - $mask[] = Serializer::toSnakeCase($key); - } - } - - $fieldMask = $this->serializer->decodeMessage(new FieldMask, ['paths' => $mask]); - - return [ - $args, - $this->serializer->decodeMessage(new Instance, $args), - $fieldMask - ]; - } - - private function transactionSelector() - { - return new TransactionSelector([ - 'id' => self::TRANSACTION - ]); - } -} - -//@codingStandardsIgnoreStart -class GrpcStub extends Grpc -{ - public $config; - - protected function constructGapic($gapicName, array $config) - { - $this->config = $config; - - return parent::constructGapic($gapicName, $config); - } -} -//@codingStandardsIgnoreEnd diff --git a/Spanner/tests/Unit/Connection/IamDatabaseTest.php b/Spanner/tests/Unit/Connection/IamDatabaseTest.php deleted file mode 100644 index 782593d18e90..000000000000 --- a/Spanner/tests/Unit/Connection/IamDatabaseTest.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\Connection; - -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Connection\IamDatabase; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use PHPUnit\Framework\TestCase; - -/** - * @group spanner-admin - * @group spanner - */ -class IamDatabaseTest extends TestCase -{ - use StubCreationTrait; - - private $connection; - - private $iam; - - public function setUp(): void - { - $this->connection = $this->getConnStub(); - - $this->iam = TestHelpers::stub(IamDatabase::class, [$this->connection->reveal()]); - } - - /** - * @dataProvider methodProvider - */ - public function testMethods($methodName, $proxyName, $args) - { - $this->connection->$proxyName($args) - ->shouldBeCalled() - ->willReturn($args); - - $this->iam->___setProperty('connection', $this->connection->reveal()); - - $res = $this->iam->$methodName($args); - $this->assertEquals($args, $res); - } - - public function methodProvider() - { - $args = ['foo' => 'bar']; - - return [ - ['getPolicy', 'getDatabaseIamPolicy', $args], - ['setPolicy', 'setDatabaseIamPolicy', $args], - ['testPermissions', 'testDatabaseIamPermissions', $args] - ]; - } -} diff --git a/Spanner/tests/Unit/Connection/IamInstanceTest.php b/Spanner/tests/Unit/Connection/IamInstanceTest.php deleted file mode 100644 index 6e7ff67a27aa..000000000000 --- a/Spanner/tests/Unit/Connection/IamInstanceTest.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\Connection; - -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Connection\IamInstance; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use PHPUnit\Framework\TestCase; - -/** - * @group spanner-admin - * @group spanner - */ -class IamInstanceTest extends TestCase -{ - use StubCreationTrait; - - private $connection; - - private $iam; - - public function setUp(): void - { - $this->connection = $this->getConnStub(); - - $this->iam = TestHelpers::stub(IamInstance::class, [$this->connection->reveal()]); - } - - /** - * @dataProvider methodProvider - */ - public function testMethods($methodName, $proxyName, $args) - { - $this->connection->$proxyName($args) - ->shouldBeCalled() - ->willReturn($args); - - $this->iam->___setProperty('connection', $this->connection->reveal()); - - $res = $this->iam->$methodName($args); - $this->assertEquals($args, $res); - } - - public function methodProvider() - { - $args = ['foo' => 'bar']; - - return [ - ['getPolicy', 'getInstanceIamPolicy', $args], - ['setPolicy', 'setInstanceIamPolicy', $args], - ['testPermissions', 'testInstanceIamPermissions', $args] - ]; - } -} diff --git a/Spanner/tests/Unit/Connection/LongRunningConnectionTest.php b/Spanner/tests/Unit/Connection/LongRunningConnectionTest.php deleted file mode 100644 index 6917e47c3ef5..000000000000 --- a/Spanner/tests/Unit/Connection/LongRunningConnectionTest.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\Connection; - -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Connection\LongRunningConnection; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use PHPUnit\Framework\TestCase; - -/** - * @group spanner - * @group spanner-admin - */ -class LongRunningConnectionTest extends TestCase -{ - use StubCreationTrait; - - private $connection; - private $lro; - - public function setUp(): void - { - $this->connection = $this->getConnStub(); - $this->lro = TestHelpers::stub(LongRunningConnection::class, [ - $this->connection->reveal() - ]); - } - - /** - * @dataProvider methodProvider - */ - public function testMethods($methodName, $proxyName, $args) - { - $this->connection->$proxyName($args) - ->shouldBeCalled() - ->willReturn($args); - - $this->lro->___setProperty('connection', $this->connection->reveal()); - - $res = $this->lro->$methodName($args); - $this->assertEquals($args, $res); - } - - public function methodProvider() - { - $args = ['foo' => 'bar']; - - return [ - ['get', 'getOperation', $args], - ['cancel', 'cancelOperation', $args], - ['delete', 'deleteOperation', $args], - ['operations', 'listOperations', $args] - ]; - } -} diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index a63c361fc6a8..0c33ecdaad14 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -17,22 +17,29 @@ namespace Google\Cloud\Spanner\Tests\Unit; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; use Google\ApiCore\ServerStream; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\AbortedException; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Exception\ServerException; -use Google\Cloud\Core\Iam\Iam; +use Google\Cloud\Core\Exception\ServiceException; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; +use Google\Cloud\Core\Testing\Snippet\Fixtures; +use Google\Cloud\Spanner\Admin\Database\V1\Backup; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; -use Google\Cloud\Spanner\Connection\ConnectionInterface; -use Google\Cloud\Spanner\Connection\Grpc; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlResponse; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; @@ -40,23 +47,41 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\BatchWriteRequest; +use Google\Cloud\Spanner\V1\BatchWriteRequest\MutationGroup; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\Mutation; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\ReadRequest; use Google\Cloud\Spanner\V1\ResultSet; +use Google\Cloud\Spanner\V1\ResultSetMetadata; use Google\Cloud\Spanner\V1\ResultSetStats; -use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType; use Google\Cloud\Spanner\V1\Session as SessionProto; -use Google\Cloud\Spanner\V1\SpannerClient; +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; +use Google\Cloud\Spanner\V1\TransactionSelector; +use Google\Cloud\Spanner\V1\Type as TypeProto; +use Google\Protobuf\Duration; +use Google\Protobuf\ListValue; +use Google\Protobuf\Timestamp as TimestampProto; +use Google\Protobuf\Value; use Google\Rpc\Code; +use Google\Rpc\Status; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Google\Cloud\Core\Exception\ServiceException; /** * @group spanner @@ -65,10 +90,9 @@ class DatabaseTest extends TestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; + use ApiHelperTrait; const PROJECT = 'my-awesome-project'; const DATABASE = 'my-database'; @@ -81,71 +105,70 @@ class DatabaseTest extends TestCase const TIMESTAMP = '2017-01-09T18:05:22.534799Z'; const BEGIN_RW_OPTIONS = ['begin' => ['readWrite' => []]]; - private $connection; + private const DIRECTED_READ_OPTIONS_INCLUDE_REPLICAS = [ + 'includeReplicas' => [ + 'autoFailoverDisabled' => false, + 'replicaSelections' => [ + [ + 'location' => 'us-central1', + 'type' => ReplicaType::READ_WRITE, + ] + ] + ] + ]; + + private const DIRECTED_READ_OPTIONS_EXCLUDE_REPLICAS = [ + 'excludeReplicas' => [ + 'replicaSelections' => [ + [ + 'location' => 'us-central1', + 'type' => ReplicaType::READ_WRITE, + ] + ] + ] + ]; + + private $spannerClient; + private $instanceAdminClient; + private $databaseAdminClient; + private $serializer; private $instance; private $sessionPool; - private $lro; - private $lroCallables; private $database; private $session; - private $databaseWithDatabaseRole; - private $directedReadOptionsIncludeReplicas; - private $directedReadOptionsExcludeReplicas; - + private $operationResponse; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->prophesize(ConnectionInterface::class); + $this->serializer = new Serializer(); $this->sessionPool = $this->prophesize(SessionPoolInterface::class); - $this->lro = $this->prophesize(LongRunningConnectionInterface::class); - $this->lroCallables = []; - $this->session = TestHelpers::stub(Session::class, [ - $this->connection->reveal(), + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + + $this->session = new Session( + $this->spannerClient->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION - ]); - $this->directedReadOptionsIncludeReplicas = [ - 'includeReplicas' => [ - 'autoFailoverDisabled' => false, - 'replicaSelections' => [ - [ - 'location' => 'us-central1', - 'type' => ReplicaType::READ_WRITE, - - ] - ] - ] - ]; - $this->directedReadOptionsExcludeReplicas = [ - 'excludeReplicas' => [ - 'autoFailoverDisabled' => false, - 'replicaSelections' => [ - [ - 'location' => 'us-central1', - 'type' => ReplicaType::READ_WRITE, - ] - ] - ] - ]; + ); - $this->instance = TestHelpers::stub(Instance::class, [ - $this->connection->reveal(), - $this->lro->reveal(), - $this->lroCallables, + $this->instance = new Instance( + $this->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE, false, [], - ['directedReadOptions' => $this->directedReadOptionsIncludeReplicas] - ], [ - 'info', - 'connection' - ]); + ['directedReadOptions' => self::DIRECTED_READ_OPTIONS_INCLUDE_REPLICAS] + ); $this->sessionPool->acquire(Argument::type('string')) ->willReturn($this->session); @@ -154,26 +177,22 @@ public function setUp(): void $this->sessionPool->release(Argument::type(Session::class)) ->willReturn(null); - $args = [ - $this->connection->reveal(), + $this->database = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, $this->instance, - $this->lro->reveal(), - $this->lroCallables, self::PROJECT, self::DATABASE, $this->sessionPool->reveal(), false, [], 'Reader' - ]; - - $props = [ - 'connection', 'operation', 'session', 'sessionPool', 'instance' - ]; + ); - $this->database = TestHelpers::stub(Database::class, $args, $props); - $args[6] = null; - $this->databaseWithDatabaseRole = TestHelpers::stub(Database::class, $args, $props); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); } public function testName() @@ -186,17 +205,17 @@ public function testName() public function testInfo() { - $res = [ - 'name' => $this->database->name() - ]; - - $this->connection->getDatabase(Argument::withEntry('name', $this->database->name())) - ->shouldBeCalledTimes(1) - ->willReturn($res); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabase( + Argument::that(function (GetDatabaseRequest $request) { + return $request->getName() === $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new DatabaseProto(['name' => $this->database->name()])); - $this->assertEquals($res, $this->database->info()); + $this->assertArrayHasKey('name', $this->database->info()); + $this->assertEquals($this->database->info()['name'], $this->database->name()); // Make sure the request only is sent once. $this->database->info(); @@ -207,11 +226,14 @@ public function testState() $res = [ 'state' => Database::STATE_READY ]; - $this->connection->getDatabase(Argument::withEntry('name', $this->database->name())) - ->shouldBeCalledTimes(1) - ->willReturn($res); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabase( + Argument::that(function (GetDatabaseRequest $request) { + return $request->getName() === $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new DatabaseProto($res)); $this->assertEquals(Database::STATE_READY, $this->database->state()); @@ -222,48 +244,61 @@ public function testState() public function testCreateBackup() { $expireTime = new \DateTime(); - $this->connection->createBackup(Argument::allOf( - Argument::withEntry('instance', $this->instance->name()), - Argument::withEntry('backupId', self::BACKUP), - Argument::withEntry('backup', [ - 'database' => $this->database->name(), - 'expireTime' => $expireTime->format('Y-m-d\TH:i:s.u\Z') - ]) - )) - ->shouldBeCalled() - ->willReturn(['name' => 'operations/foo']); - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createBackup( + Argument::that(function ($request) use ($expireTime) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['parent'], + $this->instance->name() + ); + $this->assertEquals($message['backupId'], self::BACKUP); + return $message['backup']['expireTime'] == $expireTime->format('Y-m-d\TH:i:s.u\Z') + && $message['backup']['database'] == $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->database->createBackup(self::BACKUP, $expireTime); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } public function testBackups() { $backups = [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup1'), - ], - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2'), - ] + new Backup(['name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup1')]), + new Backup(['name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2')]) ]; - $expectedFilter = "database:".$this->database->name(); - $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) - ->shouldBeCalled() - ->willReturn(['backups' => $backups]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListBackupsResponse(['backups' => $backups])); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + + $expectedFilter = 'database:' . $this->database->name(); + $this->databaseAdminClient->listBackups( + Argument::that(function ($request) use ($expectedFilter) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['filter'], + $expectedFilter + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($pagedListResponse->reveal()); $bkps = $this->database->backups(); - $this->assertInstanceOf(ItemIterator::class, $bkps); $bkps = iterator_to_array($bkps); - $this->assertCount(2, $bkps); $this->assertEquals('backup1', DatabaseAdminClient::parseName($bkps[0]->name())['backup']); $this->assertEquals('backup2', DatabaseAdminClient::parseName($bkps[1]->name())['backup']); @@ -271,23 +306,34 @@ public function testBackups() public function testBackupsWithCustomFilter() { - $backups = [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup1'), - ], - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2'), - ] - ]; - $defaultFilter = "database:" . $this->database->name(); - $customFilter = "customFilter"; - $expectedFilter = sprintf('(%1$s) AND (%2$s)', $defaultFilter, $customFilter); + $backup1 = DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup1'); + $backup2 = DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, 'backup2'); + $backups = [new Backup(['name' => $backup1]), new Backup(['name' => $backup2])]; - $this->connection->listBackups(Argument::withEntry('filter', $expectedFilter)) - ->shouldBeCalled() - ->willReturn(['backups' => $backups]); + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListBackupsResponse(['backups' => $backups])); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $defaultFilter = 'database:' . $this->database->name(); + $customFilter = 'customFilter'; + $expectedFilter = sprintf('(%1$s) AND (%2$s)', $defaultFilter, $customFilter); + + $this->databaseAdminClient->listBackups( + Argument::that(function ($request) use ($expectedFilter) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['filter'], + $expectedFilter + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($pagedListResponse->reveal()); $bkps = $this->database->backups(['filter' => $customFilter]); @@ -306,13 +352,18 @@ public function testReload() 'name' => $this->database->name() ]; - $this->connection->getDatabase(Argument::withEntry('name', $this->database->name())) + $this->databaseAdminClient->getDatabase( + Argument::that(function (GetDatabaseRequest $request) { + return $request->getName() === $this->database->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(2) - ->willReturn($res); + ->willReturn(new DatabaseProto($res)); - $this->database->___setProperty('connection', $this->connection->reveal()); - - $this->assertEquals($res, $this->database->reload()); + $info = $this->database->reload(); + $this->assertArrayHasKey('name', $info); + $this->assertEquals($info['name'], $this->database->name()); // Make sure the request is sent each time the method is called. $this->database->reload(); @@ -323,12 +374,14 @@ public function testReload() */ public function testExists() { - $this->connection->getDatabase(Argument::withEntry( - 'name', - DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ))->shouldBeCalled()->willReturn([]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabase( + Argument::that(function (GetDatabaseRequest $request) { + return $request->getName() === $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new DatabaseProto()); $this->assertTrue($this->database->exists()); } @@ -338,12 +391,15 @@ public function testExists() */ public function testExistsNotFound() { - $this->connection->getDatabase(Argument::withEntry('name', $this->database->name())) - ->shouldBeCalled() + $this->databaseAdminClient->getDatabase( + Argument::that(function (GetDatabaseRequest $request) { + return $request->getName() === $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willThrow(new NotFoundException('', 404)); - $this->database->___setProperty('connection', $this->connection->reveal()); - $this->assertFalse($this->database->exists()); } @@ -352,16 +408,27 @@ public function testExistsNotFound() */ public function testCreate() { - $this->connection->createDatabase(Argument::allOf( - Argument::withEntry('createStatement', 'CREATE DATABASE `my-database`'), - Argument::withEntry('extraStatements', [ - 'CREATE TABLE bar' - ]) - ))->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createDatabase( + Argument::that(function ($request) { + $createStatement = $request->getCreateStatement(); + $extraStatements = $request->getExtraStatements(); + $this->assertStringContainsString('my-database', $createStatement); + $this->assertEquals(['CREATE TABLE bar'], iterator_to_array($extraStatements)); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); + + new OperationResponse('my-operation', new DatabaseAdminClient([ + 'credentials' => Fixtures::KEYFILE_STUB_FIXTURE() + ]), [ + 'lastProtoResponse' => $this->serializer->decodeMessage( + new DatabaseProto(), + ['name' => 'my-database'] + ) + ]); $op = $this->database->create([ 'statements' => [ @@ -369,7 +436,7 @@ public function testCreate() ] ]); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } /** @@ -377,20 +444,23 @@ public function testCreate() */ public function testUpdateDatabase() { - $this->connection->updateDatabase(Argument::allOf( - Argument::withEntry('database', [ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), - 'enableDropProtection' => true, - ]), - Argument::withEntry('updateMask', ['paths' => ['enable_drop_protection']]) - ))->shouldBeCalledTimes(1)->willReturn([ - 'enableDropProtection' => true - ]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->updateDatabase( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database']['name'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + $this->assertEquals($message['updateMask'], ['paths' => ['enable_drop_protection']]); + return $message['database']['enableDropProtection']; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); - $res = $this->database->updateDatabase(['enableDropProtection' => true]); - $this->assertTrue($res['enableDropProtection']); + $op = $this->database->updateDatabase(['enableDropProtection' => true]); + $this->assertInstanceOf(OperationResponse::class, $op); } /** @@ -400,20 +470,23 @@ public function testCreatePostgresDialect() { $createStatement = sprintf('CREATE DATABASE "%s"', self::DATABASE); - $this->connection->createDatabase(Argument::allOf( - Argument::withEntry('createStatement', $createStatement), - Argument::withEntry('extraStatements', []) - ))->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createDatabase( + Argument::that(function ($request) use ($createStatement) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['createStatement'], $createStatement); + $this->assertEmpty($message['extraStatements']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->database->create([ - 'databaseDialect'=> DatabaseDialect::POSTGRESQL + 'databaseDialect' => DatabaseDialect::POSTGRESQL ]); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } /** @@ -422,20 +495,25 @@ public function testCreatePostgresDialect() public function testRestoreFromBackupName() { $backupName = DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP); - $this->connection->restoreDatabase(Argument::allOf( - Argument::withEntry('instance', $this->instance->name()), - Argument::withEntry('databaseId', self::DATABASE), - Argument::withEntry('backup', $backupName) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->restoreDatabase( + Argument::that(function ($request) use ($backupName) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['parent'], + $this->instance->name() + ); + $this->assertEquals($message['databaseId'], self::DATABASE); + $this->assertEquals($message['backup'], $backupName); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->database->restore($backupName); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } /** @@ -445,20 +523,24 @@ public function testRestoreFromBackupObject() { $backupObj = $this->instance->backup(self::BACKUP); - $this->connection->restoreDatabase(Argument::allOf( - Argument::withEntry('instance', $this->instance->name()), - Argument::withEntry('databaseId', self::DATABASE), - Argument::withEntry('backup', $backupObj->name()) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->restoreDatabase( + Argument::that(function ($request) use ($backupObj) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['parent'], + $this->instance->name() + ); + $this->assertEquals($message['databaseId'], self::DATABASE); + $this->assertEquals($message['backup'], $backupObj->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->database->restore($backupObj); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } /** @@ -467,34 +549,46 @@ public function testRestoreFromBackupObject() public function testUpdateDdl() { $statement = 'foo'; - $this->connection->updateDatabaseDdl([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), - 'statements' => [$statement] - ])->willReturn([ - 'name' => 'my-operation' - ]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->updateDatabaseDdl( + Argument::that(function ($request) use ($statement) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + $this->assertEquals($message['statements'], [$statement]); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $res = $this->database->updateDdl($statement); - $this->assertInstanceOf(LongRunningOperation::class, $res); + $this->assertInstanceOf(OperationResponse::class, $res); } - /** * @group spanner-admin */ public function testUpdateDdlBatch() { $statements = ['foo', 'bar']; - $this->connection->updateDatabaseDdl([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), - 'statements' => $statements - ])->willReturn([ - 'name' => 'my-operation' - ])->shouldBeCalled(); - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->updateDatabaseDdl( + Argument::that(function ($request) use ($statements) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + $this->assertEquals($message['statements'], $statements); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $this->database->updateDdlBatch($statements); } @@ -505,15 +599,24 @@ public function testUpdateDdlBatch() public function testUpdateWithSingleStatement() { $statement = 'foo'; - $this->connection->updateDatabaseDdl([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), - 'statements' => ['foo'] - ])->shouldBeCalled()->willReturn(['name' => 'operations/foo']); - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->updateDatabaseDdl( + Argument::that(function ($request) use ($statement) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + $this->assertEquals($message['statements'], [$statement]); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $res = $this->database->updateDdl($statement); - $this->assertInstanceOf(LongRunningOperation::class, $res); + $this->assertInstanceOf(OperationResponse::class, $res); } /** @@ -521,14 +624,21 @@ public function testUpdateWithSingleStatement() */ public function testDrop() { - $this->connection->dropDatabase([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ])->shouldBeCalled(); + $this->databaseAdminClient->dropDatabase( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); $this->sessionPool->clear()->shouldBeCalled()->willReturn(null); - $this->database->___setProperty('connection', $this->connection->reveal()); - $this->database->drop(); } @@ -537,53 +647,56 @@ public function testDrop() */ public function testDropDeleteSession() { - $this->connection->createSession(Argument::withEntry('database', $this->database->name())) - ->shouldBeCalled() - ->willReturn([ - 'name' => $this->session->name() - ]); - - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->createSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['database'] == $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new SessionProto(['name' => $this->session->name()])); - $this->connection->deleteSession(Argument::allOf( - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ), - Argument::withEntry('name', $this->session->name()) - )) - ->shouldBeCalled(); + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $this->connection->dropDatabase([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ])->shouldBeCalled(); + $this->spannerClient->deleteSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['name'] == $this->session->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); + + $this->databaseAdminClient->dropDatabase( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $database = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, $this->instance, - $this->lro->reveal(), - $this->lroCallables, self::PROJECT, self::DATABASE - ]); + ); // This will set a session on the Database class. $database->transaction(); @@ -597,11 +710,19 @@ public function testDropDeleteSession() public function testDdl() { $ddl = ['create table users', 'create table posts']; - $this->connection->getDatabaseDDL([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ])->willReturn(['statements' => $ddl]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabaseDdl( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseDdlResponse(['statements' => $ddl])); $this->assertEquals($ddl, $this->database->ddl()); } @@ -611,11 +732,19 @@ public function testDdl() */ public function testDdlNoResult() { - $this->connection->getDatabaseDDL([ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ])->willReturn([]); - - $this->database->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabaseDdl( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['database'], + DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new GetDatabaseDdlResponse()); $this->assertEquals([], $this->database->ddl()); } @@ -625,26 +754,20 @@ public function testDdlNoResult() */ public function testIam() { - $this->assertInstanceOf(Iam::class, $this->database->iam()); + $this->assertInstanceOf(IamManager::class, $this->database->iam()); } public function testSnapshot() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) - ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $res = $this->database->snapshot(); $this->assertInstanceOf(Snapshot::class, $res); @@ -671,13 +794,9 @@ public function testSnapshotNestedTransaction() // Begin transaction RPC is skipped when begin is inlined // and invoked only if `begin` fails or if commit is the // sole operation in the transaction. - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); - - $this->connection->rollback(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); $this->database->runTransaction(function ($t) { $this->database->snapshot(); @@ -686,28 +805,26 @@ public function testSnapshotNestedTransaction() public function testBatchWrite() { - $expectedMutationGroup = ['mutations' => [ - [ - Operation::OP_INSERT_OR_UPDATE => [ - 'table' => 'foo', - 'columns' => ['bar1', 'bar2'], - 'values' => [1, 2] - ] - ] - ]]; - $this->connection->batchWrite(Argument::allOf( - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ), - Argument::withEntry('session', $this->session->name()), - Argument::withEntry('mutationGroups', [$expectedMutationGroup]) - ))->shouldBeCalled()->willReturn(['foo result']); - + $expectedMutationGroup = new MutationGroup(['mutations' => [ + new Mutation(['insert_or_update' => new Mutation\Write([ + 'table' => 'foo', + 'columns' => ['bar1', 'bar2'], + 'values' => [new ListValue(['values' => [ + new Value(['string_value' => '1']), + new Value(['string_value' => '2']), + ]])] + ])]) + ]]); + + $this->spannerClient->batchWrite( + Argument::that(function ($request) use ($expectedMutationGroup) { + return $request->getSession() === $this->session->name() + && $request->getMutationGroups()[0] == $expectedMutationGroup; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $mutationGroups = [ ($this->database->mutationGroup(false)) @@ -717,49 +834,13 @@ public function testBatchWrite() ) ]; - $this->refreshOperation($this->database, $this->connection->reveal()); - $result = $this->database->batchWrite($mutationGroups); - $this->assertIsArray($result); + $this->assertEquals('10', iterator_to_array($result)[0]['values'][0]); } public function testRunTransaction() { - $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, - ]) - )) - ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->connection->commit(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, - ]) - )) - ->shouldBeCalled() - ->willReturn(['commitTimestamp' => '2017-01-09T18:05:22.534799Z']); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->stubCommit(false); $hasTransaction = false; @@ -776,12 +857,9 @@ public function testRunTransactionNoCommit() { $this->expectException(\InvalidArgumentException::class); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->rollback(Argument::any())->shouldNotBeCalled(); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); $this->database->runTransaction($this->noop()); } @@ -790,13 +868,9 @@ public function testRunTransactionNestedTransaction() { $this->expectException(\BadMethodCallException::class); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); - - $this->connection->rollback(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); $this->database->runTransaction(function ($t) { $this->database->runTransaction($this->noop()); @@ -809,23 +883,19 @@ public function testRunTransactionShouldRetryOnRstStreamErrors() $this->expectExceptionMessage('RST_STREAM'); $err = new ServerException('RST_STREAM', Code::INTERNAL); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(3) ->willThrow($err); $this->database->runTransaction(function ($t) { $t->commit(); - }, ['maxRetries' => 2]); + }, ['retrySettings' => ['maxRetries' => 2]]); } public function testRunTransactionRetry() @@ -839,51 +909,41 @@ public function testRunTransactionRetry() ] ]); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(3) - ->willReturn(['id' => self::TRANSACTION]); + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $it = 0; - $this->connection->commit(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $commitResponse = $this->commitResponse(); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(3) - ->will(function () use (&$it, $abort) { + ->will(function () use (&$it, $abort, $commitResponse) { $it++; if ($it <= 2) { throw $abort; } - return ['commitTimestamp' => TransactionTest::TIMESTAMP]; + return $commitResponse; }); - $this->refreshOperation($this->database, $this->connection->reveal()); - $this->database->runTransaction(function ($t) use (&$it) { if ($it > 0) { $this->assertTrue($t->isRetry()); } else { $this->assertFalse($t->isRetry()); } - $t->commit(); }); } @@ -901,45 +961,33 @@ public function testRunTransactionAborted() ] ]); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(Database::MAX_RETRIES + 1) - ->willReturn(['id' => self::TRANSACTION]); + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $it = 0; - $this->connection->commit(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(Database::MAX_RETRIES + 1) ->will(function () use (&$it, $abort) { $it++; - if ($it <= Database::MAX_RETRIES + 1) { throw $abort; } - return ['commitTimestamp' => TransactionTest::TIMESTAMP]; }); - $this->refreshOperation($this->database, $this->connection->reveal()); - $this->database->runTransaction(function ($t) { $t->commit(); }); @@ -947,24 +995,19 @@ public function testRunTransactionAborted() public function testTransaction() { - $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, - ]) - )) - ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['requestOptions']['transactionTag' ], + self::TRANSACTION_TAG, + ); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $t = $this->database->transaction(['tag' => self::TRANSACTION_TAG]); $this->assertInstanceOf(Transaction::class, $t); @@ -974,13 +1017,9 @@ public function testTransactionNestedTransaction() { $this->expectException(\BadMethodCallException::class); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); - - $this->connection->rollback(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); $this->database->runTransaction(function ($t) { $this->database->transaction(); @@ -992,23 +1031,28 @@ public function testInsert() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][OPERATION::OP_INSERT]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][OPERATION::OP_INSERT]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][OPERATION::OP_INSERT]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_INSERT]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->insert($table, $row); $this->assertInstanceOf(Timestamp::class, $res); @@ -1020,23 +1064,28 @@ public function testInsertBatch() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][OPERATION::OP_INSERT]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][OPERATION::OP_INSERT]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][OPERATION::OP_INSERT]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_INSERT]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->insertBatch($table, [$row]); $this->assertInstanceOf(Timestamp::class, $res); @@ -1048,23 +1097,28 @@ public function testUpdate() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_UPDATE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_UPDATE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_UPDATE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_UPDATE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_UPDATE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_UPDATE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->update($table, $row); $this->assertInstanceOf(Timestamp::class, $res); @@ -1076,23 +1130,28 @@ public function testUpdateBatch() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_UPDATE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_UPDATE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_UPDATE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_UPDATE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_UPDATE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_UPDATE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->updateBatch($table, [$row]); $this->assertInstanceOf(Timestamp::class, $res); @@ -1104,23 +1163,28 @@ public function testInsertOrUpdate() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->insertOrUpdate($table, $row); $this->assertInstanceOf(Timestamp::class, $res); @@ -1132,23 +1196,28 @@ public function testInsertOrUpdateBatch() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_INSERT_OR_UPDATE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_INSERT_OR_UPDATE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->insertOrUpdateBatch($table, [$row]); $this->assertInstanceOf(Timestamp::class, $res); @@ -1160,23 +1229,28 @@ public function testReplace() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_REPLACE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_REPLACE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_REPLACE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_REPLACE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_REPLACE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_REPLACE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->replace($table, $row); $this->assertInstanceOf(Timestamp::class, $res); @@ -1188,23 +1262,28 @@ public function testReplaceBatch() $table = 'foo'; $row = ['col' => 'val']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $row) { - if ($arg['mutations'][0][Operation::OP_REPLACE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $row) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_REPLACE]['columns'][0] !== array_keys($row)[0]) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_REPLACE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_REPLACE]['values'][0] !== current($row)) { - return false; - } + if ($request['mutations'][0][OPERATION::OP_REPLACE]['columns'][0] !== array_keys($row)[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][OPERATION::OP_REPLACE]['values'][0][0] !== current($row)) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->replaceBatch($table, [$row]); $this->assertInstanceOf(Timestamp::class, $res); @@ -1216,23 +1295,28 @@ public function testDelete() $table = 'foo'; $keys = [10, 'bar']; - $this->connection->commit(Argument::that(function ($arg) use ($table, $keys) { - if ($arg['mutations'][0][Operation::OP_DELETE]['table'] !== $table) { - return false; - } + $this->spannerClient->commit( + Argument::that(function ($request) use ($table, $keys) { + $request = $this->serializer->encodeMessage($request); - if ($arg['mutations'][0][Operation::OP_DELETE]['keySet']['keys'][0] !== (string) $keys[0]) { - return false; - } + if ($request['mutations'][0][Operation::OP_DELETE]['table'] !== $table) { + return false; + } - if ($arg['mutations'][0][Operation::OP_DELETE]['keySet']['keys'][1] !== $keys[1]) { - return false; - } + if ($request['mutations'][0][Operation::OP_DELETE]['keySet']['keys'][0][0] !== (string) $keys[0]) { + return false; + } - return true; - }))->shouldBeCalled()->willReturn($this->commitResponse()); + if ($request['mutations'][0][Operation::OP_DELETE]['keySet']['keys'][1][0] !== $keys[1]) { + return false; + } - $this->refreshOperation($this->database, $this->connection->reveal()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $res = $this->database->delete($table, new KeySet(['keys' => $keys])); $this->assertInstanceOf(Timestamp::class, $res); @@ -1243,12 +1327,23 @@ public function testExecute() { $sql = 'SELECT * FROM Table'; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) use ($sql) { + return $request->getSql() == $sql; + }), + Argument::that(function ($callOptions) { + $this->assertArrayHasKey('headers', $callOptions); + $this->assertArrayHasKey('x-goog-spanner-route-to-leader', $callOptions['headers']); + $this->assertEquals(['true'], $callOptions['headers']['x-goog-spanner-route-to-leader']); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_lower_bound' => 1]), + self::TRANSACTION + )); $res = $this->database->execute($sql, [ 'transactionType' => SessionPoolInterface::CONTEXT_READWRITE @@ -1260,16 +1355,17 @@ public function testExecute() public function testExecuteWithSingleSession() { - $this->database->___setProperty('sessionPool', null); - $this->database->___setProperty('session', $this->session); $sql = 'SELECT * FROM Table'; $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); - $this->connection->executeStreamingSql(Argument::withEntry('session', $sessName)) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) use ($sessName) { + return $request->getSession() == $sessName; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->database->execute($sql); $rows = iterator_to_array($res->rows()); @@ -1277,19 +1373,20 @@ public function testExecuteWithSingleSession() public function testExecuteSingleUseMaxStaleness() { - $this->database->___setProperty('sessionPool', null); - $this->database->___setProperty('session', $this->session); $sql = 'SELECT * FROM Table'; $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); - $this->connection->executeStreamingSql(Argument::withEntry('session', $sessName)) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) use ($sessName) { + return $request->getSession() == $sessName; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->database->execute($sql, [ - 'maxStaleness' => new Duration(10, 0) + 'maxStaleness' => new Duration(['seconds' => 10, 'nanos' => 0]) ]); $rows = iterator_to_array($res->rows()); } @@ -1298,35 +1395,52 @@ public function testExecuteBeginMaxStalenessFails() { $this->expectException(\BadMethodCallException::class); - $this->database->___setProperty('sessionPool', null); - $this->database->___setProperty('session', $this->session); $sql = 'SELECT * FROM Table'; $this->database->execute($sql, [ 'begin' => true, - 'maxStaleness' => new Duration(10, 0) + 'maxStaleness' => new Duration(['seconds' => 10, 'nanos' => 0]) ]); } public function testExecutePartitionedUpdate() { $sql = 'UPDATE foo SET bar = @bar'; - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('transactionOptions', [ - 'partitionedDml' => [] - ]), - Argument::withEntry('singleUse', false) - ))->shouldBeCalled()->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['options']['partitionedDml' ], + [] + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true)); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['sql'], $sql); + $this->assertEquals($message['transaction'], ['id' => self::TRANSACTION]); + return true; + }), + Argument::that(function ($callOptions) { + $this->assertArrayHasKey('headers', $callOptions); + $this->assertArrayHasKey('x-goog-spanner-route-to-leader', $callOptions['headers']); + $this->assertEquals(['true'], $callOptions['headers']['x-goog-spanner-route-to-leader']); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_lower_bound' => 1]), + true + )); - $this->refreshOperation($this->database, $this->connection->reveal()); $res = $this->database->executePartitionedUpdate($sql); $this->assertEquals(1, $res); @@ -1337,27 +1451,21 @@ public function testRead() $table = 'Table'; $opts = ['foo' => 'bar']; - $this->connection->streamingRead(Argument::that(function ($arg) use ($table) { - if ($arg['table'] !== $table) { - return false; - } - - if ($arg['keySet']['all'] !== true) { - return false; - } - - if ($arg['columns'] !== ['ID']) { - return false; - } - - if ($arg['headers'] !== ['x-goog-spanner-route-to-leader' => ['true']]) { - return false; - } - - return true; - }))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function (ReadRequest $request) use ($table) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($request->getTable(), $table); + $this->assertEquals( + $message['keySet'], + ['all' => true, 'keys' => [], 'ranges' => []] + ); + $this->assertEquals($message['columns'], ['ID']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->database->read( $table, @@ -1377,57 +1485,82 @@ public function testSessionPool() public function testClose() { + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); + $this->sessionPool->release(Argument::type(Session::class)) ->shouldBeCalled() ->willReturn(null); - $this->database->___setProperty('sessionPool', $this->sessionPool->reveal()); - $this->database->___setProperty('session', $this->session); + // start a transaction to create a session + $this->database->transaction(); $this->database->close(); - - $this->assertNull($this->database->___getProperty('session')); } public function testCloseNoPool() { - $this->connection->deleteSession(Argument::allOf( - Argument::withEntry('name', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) - ->shouldBeCalled() - ->willReturn([]); + $database = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance, + self::PROJECT, + self::DATABASE + ); + + $this->spannerClient->createSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['database'] == $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new SessionProto(['name' => $this->session->name()])); + + $this->spannerClient->deleteSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['name'] == $this->session->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); + + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $this->session->___setProperty('connection', $this->connection->reveal()); - $this->database->___setProperty('sessionPool', null); - $this->database->___setProperty('session', $this->session); + // start a transaction to create a session + $database->transaction(); $this->database->close(); } public function testCreateSession() { - $db = SpannerClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE); - $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); - $this->connection->createSession(Argument::withEntry('database', $db)) - ->shouldBeCalled() - ->willReturn([ - 'name' => $sessName - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->createSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['database'] == $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new SessionProto(['name' => $this->session->name()])); $sess = $this->database->createSession(); $this->assertInstanceOf(Session::class, $sess); - $this->assertEquals($sessName, $sess->name()); + $this->assertEquals($this->session->name(), $sess->name()); } public function testSession() @@ -1452,22 +1585,22 @@ public function testIdentity() ], $this->database->identity()); } - public function testConnection() - { - $this->assertInstanceOf(ConnectionInterface::class, $this->database->connection()); - } - // ******* // Helpers private function commitResponse() { - return ['commitTimestamp' => '2017-01-09T18:05:22.534799Z']; + return new CommitResponse([ + 'commit_timestamp' => new TimestampProto([ + 'seconds' => (new \DateTime(self::TIMESTAMP))->format('U'), + 'nanos' => 534799000 + ]) + ]); } private function assertTimestampIsCorrect($res) { - $ts = new \DateTimeImmutable($this->commitResponse()['commitTimestamp']); + $ts = new \DateTimeImmutable(self::TIMESTAMP); $this->assertEquals($ts->format('Y-m-d\TH:i:s\Z'), $res->get()->format('Y-m-d\TH:i:s\Z')); } @@ -1481,29 +1614,63 @@ private function noop() public function testDBDatabaseRole() { + $this->spannerClient->createSession( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['session']['creatorRole'], 'Reader'); + return $message['database'] == $this->database->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new SessionProto(['name' => $this->session->name()])); + $sql = $this->createStreamingAPIArgs()['sql']; - $this->connection->createSession(Argument::withEntry( - 'session', - ['labels' => [], 'creator_role' => 'Reader'] - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => $this->session->name() - ]); - $this->connection->executeStreamingSql(Argument::withEntry('sql', $sql)) - ->shouldBeCalled()->willReturn($this->resultGenerator()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $this->assertEquals($request->getSql(), $sql); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); + + $this->spannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + )->shouldBeCalledOnce(); - $this->databaseWithDatabaseRole->execute($sql); + $databaseWithDatabaseRole = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, + $this->instance, + self::PROJECT, + self::DATABASE, + null, + false, + [], + 'Reader' + ); + $databaseWithDatabaseRole->execute($sql); } public function testExecuteWithDirectedRead() { - $this->connection->executeStreamingSql(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsIncludeReplicas - )) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + self::DIRECTED_READ_OPTIONS_INCLUDE_REPLICAS + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $sql = 'SELECT * FROM Table'; $res = $this->database->execute($sql); @@ -1514,17 +1681,24 @@ public function testExecuteWithDirectedRead() public function testPrioritizeExecuteDirectedReadOptions() { - $this->connection->executeStreamingSql(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsExcludeReplicas - )) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + self::DIRECTED_READ_OPTIONS_EXCLUDE_REPLICAS + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $sql = 'SELECT * FROM Table'; $res = $this->database->execute( $sql, - ['directedReadOptions' => $this->directedReadOptionsExcludeReplicas] + ['directedReadOptions' => self::DIRECTED_READ_OPTIONS_EXCLUDE_REPLICAS] ); $this->assertInstanceOf(Result::class, $res); $rows = iterator_to_array($res->rows()); @@ -1536,12 +1710,19 @@ public function testReadWithDirectedRead() $table = 'foo'; $keys = [10, 'bar']; $columns = ['id', 'name']; - $this->connection->streamingRead(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsIncludeReplicas - )) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + self::DIRECTED_READ_OPTIONS_INCLUDE_REPLICAS + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->database->read( $table, @@ -1558,18 +1739,25 @@ public function testPrioritizeReadDirectedReadOptions() $table = 'foo'; $keys = [10, 'bar']; $columns = ['id', 'name']; - $this->connection->streamingRead(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsExcludeReplicas - )) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + self::DIRECTED_READ_OPTIONS_EXCLUDE_REPLICAS + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->database->read( $table, new KeySet(['keys' => $keys]), $columns, - ['directedReadOptions' => $this->directedReadOptionsExcludeReplicas] + ['directedReadOptions' => self::DIRECTED_READ_OPTIONS_EXCLUDE_REPLICAS] ); $this->assertInstanceOf(Result::class, $res); $rows = iterator_to_array($res->rows()); @@ -1582,7 +1770,6 @@ public function testRunTransactionWithUpdate() $this->stubCommit(); $this->stubExecuteStreamingSql(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($sql) { $t->executeUpdate($sql); @@ -1597,7 +1784,6 @@ public function testRunTransactionWithQuery() $this->stubCommit(); $this->stubExecuteStreamingSql(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($sql) { $t->execute($sql)->rows()->current(); @@ -1613,7 +1799,6 @@ public function testRunTransactionWithRead() $this->stubCommit(); $this->stubStreamingRead(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($keySet, $cols) { $t->read(self::TEST_TABLE_NAME, $keySet, $cols)->rows()->current(); @@ -1628,7 +1813,6 @@ public function testRunTransactionWithUpdateBatch() $this->stubCommit(); $this->stubExecuteBatchDml(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($sql) { $t->executeUpdateBatch([['sql' => $sql]]); @@ -1646,7 +1830,6 @@ public function testRunTransactionWithReadFirst() $this->stubCommit(); $this->stubStreamingRead(); $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($keySet, $cols, $sql) { $t->read(self::TEST_TABLE_NAME, $keySet, $cols)->rows()->current(); @@ -1665,7 +1848,6 @@ public function testRunTransactionWithExecuteFirst() $this->stubCommit(); $this->stubStreamingRead(['id' => self::TRANSACTION]); $this->stubExecuteStreamingSql(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($keySet, $cols, $sql) { $t->execute($sql)->rows()->current(); @@ -1685,7 +1867,6 @@ public function testRunTransactionWithUpdateBatchFirst() $this->stubExecuteBatchDml(); $this->stubStreamingRead(['id' => self::TRANSACTION]); $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function (Transaction $t) use ($keySet, $cols, $sql) { $t->executeUpdateBatch([['sql' => $sql]]); @@ -1705,16 +1886,37 @@ public function testRunTransactionWithUpdateBatchError() $this->stubCommit(); $this->stubStreamingRead(['id' => self::TRANSACTION]); $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); - $this->refreshOperation($this->database, $this->connection->reveal()); - $this->connection->executeBatchDml(Argument::allOf( - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]), - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - ))->shouldBeCalled()->willReturn([ - 'status' => ['code' => Code::INVALID_ARGUMENT], - 'resultSets' => [['metadata' => ['transaction' => ['id' => self::TRANSACTION]]]] - ]); + $this->spannerClient->executeBatchDml( + Argument::that(function (ExecuteBatchDmlRequest $request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['requestOptions']['transactionTag'], + self::TRANSACTION_TAG + ); + $this->assertEquals([ + 'begin' => [ + 'readWrite' => [ + 'readLockMode' => 0, + 'multiplexedSessionPreviousTransactionId' => '', + ], + 'excludeTxnFromChangeStreams' => false + ] + ], $message['transaction']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteBatchDmlResponse([ + 'status' => new Status(['code' => Code::INVALID_ARGUMENT]), + 'result_sets' => [ + new ResultSet([ + 'metadata' => new ResultSetMetadata([ + 'transaction' => new TransactionProto(['id' => self::TRANSACTION]) + ]) + ]) + ] + ])); $this->database->runTransaction(function (Transaction $t) use ($keySet, $cols, $sql) { $result = $t->executeUpdateBatch([['sql' => $sql], ['sql' => $sql]]); @@ -1732,17 +1934,50 @@ public function testRunTransactionWithFirstFailedStatement() $error = new ServerException('RST_STREAM', Code::INTERNAL); // First call with ILB fails - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]) - ))->shouldBeCalled()->willThrow($error); - $this->stubCommit(false); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($sql, $message['sql']); + $this->assertEquals( + $message['requestOptions']['transactionTag'], + self::TRANSACTION_TAG + ); + return $message['transaction'] == [ + 'begin' => [ + 'readWrite' => [ + 'readLockMode' => 0, + 'multiplexedSessionPreviousTransactionId' => '', + ], + 'excludeTxnFromChangeStreams' => false, + ] + ]; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willThrow($error); + // Second call with non ILB return result - $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($sql, $message['sql']); + $this->assertEquals( + $message['requestOptions']['transactionTag'], + self::TRANSACTION_TAG + ); + return $message['transaction'] == ['id' => self::TRANSACTION]; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_exact' => 1]), + self::TRANSACTION + )); + + $this->stubCommit(false); $this->database->runTransaction(function ($t) use ($sql) { $t->execute($sql); @@ -1768,43 +2003,34 @@ public function testRunTransactionWithCommitAborted() $this->stubExecuteStreamingSql(); // Second onwards non ILB $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes($numOfRetries) - ->willReturn(['id' => self::TRANSACTION]); + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $it = 0; - $this->connection->commit(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $commitResponse = $this->commitResponse(); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['session'] == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalledTimes($numOfRetries + 1) - ->will(function () use (&$it, $abort, $numOfRetries) { + ->will(function () use (&$it, $abort, $numOfRetries, $commitResponse) { $it++; if ($it <= $numOfRetries) { throw $abort; } - return ['commitTimestamp' => TransactionTest::TIMESTAMP]; + return $commitResponse; }); - $this->refreshOperation($this->database, $this->connection->reveal()); - $this->database->runTransaction(function ($t) use ($sql) { $t->execute($sql); $t->commit(); @@ -1817,28 +2043,41 @@ public function testRunTransactionWithBeginTransactionFailure() $error = new ServerException('RST_STREAM', Code::INTERNAL); $sql = $this->createStreamingAPIArgs()['sql']; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]) - ))->shouldBeCalled()->willThrow($error); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry( - 'database', - DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ) - )) + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($sql, $message['sql']); + $this->assertEquals( + $message['requestOptions']['transactionTag'], + self::TRANSACTION_TAG + ); + return $message['transaction'] == [ + 'begin' => [ + 'readWrite' => [ + 'readLockMode' => 0, + 'multiplexedSessionPreviousTransactionId' => '' + ], + 'excludeTxnFromChangeStreams' => false + ] + ]; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willThrow($error); + + $this->spannerClient->beginTransaction( + Argument::that(function ($request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['session'], $this->session->name()); + return true; + }), + Argument::type('array') + ) ->shouldBeCalledTimes(Database::MAX_RETRIES) ->willThrow($error); - $this->connection->commit(Argument::any())->shouldNotBeCalled(); - $this->refreshOperation($this->database, $this->connection->reveal()); + + $this->spannerClient->commit(Argument::cetera())->shouldNotBeCalled(); $this->database->runTransaction(function ($t) use ($sql) { $t->execute($sql); @@ -1849,7 +2088,6 @@ public function testRunTransactionWithBeginTransactionFailure() public function testRunTransactionWithBlindCommit() { $this->stubCommit(false); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function ($t) { $t->insert('Posts', [ @@ -1865,29 +2103,45 @@ public function testRunTransactionWithUnavailableErrorRetry() { $sql = $this->createStreamingAPIArgs()['sql']; $numOfRetries = 2; - $unavailable = new ServiceException('Unavailable', 14); - $result = $this->resultGenerator(true, self::TRANSACTION); + $result = $this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_lower_bound' => 1]), + self::TRANSACTION + ); $it = 0; // First call with ILB results in unavailable error. // Second call also made with ILB, returns ResultSet. - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]) - )) + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['sql'], $sql); + $this->assertEquals( + $message['transaction'], + [ + 'begin' => [ + 'readWrite' => [ + 'readLockMode' => 0, + 'multiplexedSessionPreviousTransactionId' => '' + ], + 'excludeTxnFromChangeStreams' => false + ] + ] + ); + return $message['requestOptions']['transactionTag'] == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) ->shouldBeCalledTimes($numOfRetries) - ->will(function () use (&$it, $unavailable, $numOfRetries, $result) { + ->will(function () use (&$it, $numOfRetries, $result) { $it++; if ($it < $numOfRetries) { - throw $unavailable; + throw new ServiceException('Unavailable', 14); } return $result; }); + $this->stubCommit(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function ($t) use ($sql) { $t->execute($sql); @@ -1899,22 +2153,29 @@ public function testRunTransactionWithFirstUnavailableErrorRetry() { $sql = $this->createStreamingAPIArgs()['sql']; $unavailable = new ServiceException('Unavailable', 14); + $stream = $this->prophesize(ServerStream::class); + $stream->readAll() + ->willReturn($this->resultGeneratorWithError()); // First call with ILB results in a transaction. // Then the stream fails, Second call needs to use the // transaction created by the first call. - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]) - )) - ->shouldBeCalledTimes(1) - ->willreturn($this->resultGeneratorWithError()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $this->assertEquals($request->getSql(), $sql); + return $this->serializer->decodeMessage( + new TransactionSelector(), + self::BEGIN_RW_OPTIONS + ) == $request->getTransaction() + && $request->getRequestOptions()->getTransactionTag() == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($stream->reveal()); + $this->stubExecuteStreamingSql(['id' => self::TRANSACTION]); $this->stubCommit(); - $this->refreshOperation($this->database, $this->connection->reveal()); $this->database->runTransaction(function ($t) use ($sql) { $result = $t->execute($sql); @@ -1942,14 +2203,25 @@ public function testRunTransactionWithUnavailableAndAbortErrorRetry() $it = 0; // First call with ILB results in unavailable error. // Second call also made with ILB, gets aborted. - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('transaction', self::BEGIN_RW_OPTIONS), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]), - Argument::withEntry('table', self::TEST_TABLE_NAME), - Argument::withEntry('columns', $cols) - )) + $this->spannerClient->streamingRead( + Argument::that(function ($request) use ($cols) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['table'], self::TEST_TABLE_NAME); + $this->assertEquals($message['columns'], $cols); + return $message['transaction'] + == [ + 'begin' => [ + 'readWrite' => [ + 'readLockMode' => 0, + 'multiplexedSessionPreviousTransactionId' => '' + ], + 'excludeTxnFromChangeStreams' => false + ] + ] + && $message['requestOptions']['transactionTag'] == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) ->shouldBeCalledTimes($numOfRetries) ->will(function () use (&$it, $unavailable, $numOfRetries, $abort) { $it++; @@ -1959,34 +2231,32 @@ public function testRunTransactionWithUnavailableAndAbortErrorRetry() throw $abort; } }); + // Should retry with beginTransaction RPC. $this->stubStreamingRead(['id' => self::TRANSACTION]); - $this->connection->beginTransaction(Argument::any()) - ->willReturn(['id' => self::TRANSACTION]) - ->shouldBeCalled(); - $this->connection->commit(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('transactionId', self::TRANSACTION), - Argument::withEntry('mutations', [['insert' => [ - 'table' => self::TEST_TABLE_NAME, - 'columns' => ['ID', 'title', 'content'], - 'values' => ['10', 'My New Post', 'Hello World'] - ]]]) - )) - ->shouldBeCalledTimes(1) - ->willReturn(['commitTimestamp' => self::TIMESTAMP]); - $this->refreshOperation($this->database, $this->connection->reveal()); + + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); + + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals($request->getSession(), $this->session->name()); + $this->assertEquals($request->getTransactionId(), self::TRANSACTION); + $this->assertEquals($request->getRequestOptions()->getTransactionTag(), self::TRANSACTION_TAG); + $this->assertEquals($this->serializer->encodeMessage($request)['mutations'], [['insert' => [ + 'table' => self::TEST_TABLE_NAME, + 'columns' => ['ID', 'title', 'content'], + 'values' => [['10', 'My New Post', 'Hello World']] + ]]]); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); $this->database->runTransaction(function ($t) use ($keySet, $cols) { $t->insert(self::TEST_TABLE_NAME, [ @@ -2004,10 +2274,13 @@ public function testRunTransactionWithRollback() $sql = $this->createStreamingAPIArgs()['sql']; $this->stubExecuteStreamingSql(); - $this->connection->rollback(Argument::allOf( - Argument::withEntry('transactionId', self::TRANSACTION) - ))->shouldBeCalled()->willReturn(null); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback( + Argument::that(function ($request) use ($sql) { + return $request->getTransactionId() == self::TRANSACTION; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); $this->database->runTransaction(function (Transaction $t) use ($sql) { $t->execute($sql); @@ -2017,36 +2290,26 @@ public function testRunTransactionWithRollback() public function testRunTransactionWithExcludeTxnFromChangeStreams() { - $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->assertTrue($transactionOptions->getExcludeTxnFromChangeStreams()); - return true; - })) + $stream->readAll() ->shouldBeCalledOnce() - ->willReturn($stream->reveal()); + ->willReturn([ + new ResultSet(['stats' => new ResultSetStats(['row_count_exact' => 0])]) + ]); - $database = new Database( - new Grpc(['gapicSpannerClient' => $gapic->reveal()]), - $this->instance, - $this->lro->reveal(), - $this->lroCallables, - self::PROJECT, - self::DATABASE - ); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) { + $this->assertNotNull($transactionOptions = $request->getTransaction()->getBegin()); + $this->assertTrue($transactionOptions->getExcludeTxnFromChangeStreams()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($stream->reveal()); - $database->runTransaction( + $this->database->runTransaction( function (Transaction $t) use ($sql) { // Run a fake query $t->executeUpdate($sql); @@ -2062,25 +2325,25 @@ function (Transaction $t) use ($sql) { public function testExecutePartitionedUpdateWithExcludeTxnFromChangeStreams() { - $gapic = $this->prophesize(SpannerClient::class); - - $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); - $session = new SessionProto(['name' => $sessName]); - $gapic->createSession(Argument::cetera())->shouldBeCalled()->willReturn($session); - $gapic->deleteSession(Argument::cetera())->shouldBeCalled(); - $sql = 'SELECT example FROM sql_query'; - $resultSet = new ResultSet(['stats' => new ResultSetStats(['row_count_lower_bound' => 0])]); + $stream = $this->prophesize(ServerStream::class); - $stream->readAll()->shouldBeCalledOnce()->willReturn([$resultSet]); - $gapic->executeStreamingSql($sessName, $sql, Argument::type('array')) + $stream->readAll() + ->shouldBeCalledOnce() + ->willReturn([ + new ResultSet(['stats' => new ResultSetStats(['row_count_lower_bound' => 0])]) + ]); + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) ->shouldBeCalledOnce() ->willReturn($stream->reveal()); - $gapic->beginTransaction( - $sessName, - Argument::that(function (TransactionOptions $options) { - $this->assertTrue($options->getExcludeTxnFromChangeStreams()); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertTrue($request->getOptions()->getExcludeTxnFromChangeStreams()); return true; }), Argument::type('array') @@ -2088,16 +2351,7 @@ public function testExecutePartitionedUpdateWithExcludeTxnFromChangeStreams() ->shouldBeCalledOnce() ->willReturn(new TransactionProto(['id' => 'foo'])); - $database = new Database( - new Grpc(['gapicSpannerClient' => $gapic->reveal()]), - $this->instance, - $this->lro->reveal(), - $this->lroCallables, - self::PROJECT, - self::DATABASE - ); - - $database->executePartitionedUpdate( + $this->database->executePartitionedUpdate( $sql, ['transactionOptions' => ['excludeTxnFromChangeStreams' => true]] ); @@ -2105,36 +2359,17 @@ public function testExecutePartitionedUpdateWithExcludeTxnFromChangeStreams() public function testBatchWriteWithExcludeTxnFromChangeStreams() { - $gapic = $this->prophesize(SpannerClient::class); - - $sessName = SpannerClient::sessionName(self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION); - $session = new SessionProto(['name' => $sessName]); - $gapic->createSession(Argument::cetera())->shouldBeCalled()->willReturn($session); - $gapic->deleteSession(Argument::cetera())->shouldBeCalled(); - - $mutationGroups = []; - $gapic->batchWrite( - $sessName, - $mutationGroups, - Argument::that(function ($options) { - $this->assertArrayHasKey('excludeTxnFromChangeStreams', $options); - $this->assertTrue($options['excludeTxnFromChangeStreams']); + $this->spannerClient->batchWrite( + Argument::that(function (BatchWriteRequest $request) { + $this->assertTrue($request->getExcludeTxnFromChangeStreams()); return true; - }) + }), + Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new TransactionProto(['id' => 'foo'])); - - $database = new Database( - new Grpc(['gapicSpannerClient' => $gapic->reveal()]), - $this->instance, - $this->lro->reveal(), - $this->lroCallables, - self::PROJECT, - self::DATABASE - ); + ->willReturn($this->resultGeneratorStream()); - $database->batchWrite($mutationGroups, [ + $this->database->batchWrite([], [ 'excludeTxnFromChangeStreams' => true ]); } @@ -2153,22 +2388,20 @@ private function createStreamingAPIArgs() private function resultGeneratorWithError() { - $fields = [ + $fields = new Field([ 'name' => 'ID', - 'value' => ['code' => Database::TYPE_INT64] - ]; - $values = [10]; - $result = [ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], + 'type' => new TypeProto(['code' => Database::TYPE_INT64]) + ]); + $values = [new Value(['number_value' => 10])]; + $result = new PartialResultSet([ + 'metadata' => new ResultSetMetadata([ + 'row_type' => new StructType([ + 'fields' => [$fields] + ]), + 'transaction' => new TransactionProto(['id' => self::TRANSACTION]) + ]), 'values' => $values - ]; - $result['metadata']['transaction'] = [ - 'id' => self::TRANSACTION - ]; + ]); yield $result; throw new ServiceException('Unavailable', 14); @@ -2177,67 +2410,97 @@ private function resultGeneratorWithError() private function stubCommit($withTransaction = true) { if ($withTransaction) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); } else { - $this->connection->beginTransaction(Argument::any()) - ->willReturn(['id' => self::TRANSACTION]) - ->shouldBeCalled(); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); } - $this->connection->commit(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('transactionId', self::TRANSACTION) - )) - ->shouldBeCalled() - ->willReturn(['commitTimestamp' => self::TIMESTAMP]); + + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals($request->getSession(), $this->session->name()); + $this->assertEquals($request->getTransactionId(), self::TRANSACTION); + $this->assertEquals($request->getRequestOptions()->getTransactionTag(), self::TRANSACTION_TAG); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); } private function stubStreamingRead($transactionOptions = self::BEGIN_RW_OPTIONS) { - $keySet = $this->createStreamingAPIArgs()['keySet']; $cols = $this->createStreamingAPIArgs()['cols']; - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('transaction', $transactionOptions), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]), - Argument::withEntry('table', self::TEST_TABLE_NAME), - Argument::withEntry('columns', $cols) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true, self::TRANSACTION)); + $this->spannerClient->streamingRead( + Argument::that(function (ReadRequest $request) use ($transactionOptions, $cols) { + return $request->getTransaction() == $this->serializer->decodeMessage( + new TransactionSelector(), + $transactionOptions + ) + && $request->getTable() == self::TEST_TABLE_NAME + && iterator_to_array($request->getColumns()) == $cols + && $request->getRequestOptions()->getTransactionTag() == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_exact' => 1]), + self::TRANSACTION + )); } private function stubExecuteStreamingSql($transactionOptions = self::BEGIN_RW_OPTIONS) { $sql = $this->createStreamingAPIArgs()['sql']; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', $transactionOptions), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true, self::TRANSACTION)); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql, $transactionOptions) { + return $request->getSql() == $sql + && $request->getTransaction() == $this->serializer->decodeMessage( + new TransactionSelector(), + $transactionOptions + ) + && $request->getRequestOptions()->getTransactionTag() == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn($this->resultGeneratorStream( + null, + new ResultSetStats(['row_count_exact' => 1]), + self::TRANSACTION + )); } private function stubExecuteBatchDml($transactionOptions = self::BEGIN_RW_OPTIONS) { - $this->connection->executeBatchDml(Argument::allOf( - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG - ]), - Argument::withEntry('transaction', $transactionOptions), - ))->shouldBeCalled()->willReturn([ - 'resultSets' => [['metadata' => ['transaction' => ['id' => self::TRANSACTION]]]] - ]); + $this->spannerClient->executeBatchDml( + Argument::that(function (ExecuteBatchDmlRequest $request) use ($transactionOptions) { + $this->assertEquals( + $request->getTransaction(), + $this->serializer->decodeMessage(new TransactionSelector(), $transactionOptions) + ); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteBatchDmlResponse([ + 'result_sets' => [ + new ResultSet(['metadata' => new ResultSetMetadata([ + 'transaction' => new TransactionProto(['id' => self::TRANSACTION]) + ])]) + ] + ])); } } diff --git a/Spanner/tests/Unit/DateTest.php b/Spanner/tests/Unit/DateTest.php index 1e16f5c8360a..ee4a3d9a32cf 100644 --- a/Spanner/tests/Unit/DateTest.php +++ b/Spanner/tests/Unit/DateTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Spanner\Date; use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\Date; use PHPUnit\Framework\TestCase; /** @@ -57,7 +57,7 @@ public function testFormatAsString() public function testCast() { - $this->assertEquals($this->dt->format(Date::FORMAT), (string)$this->date); + $this->assertEquals($this->dt->format(Date::FORMAT), (string) $this->date); } public function testType() diff --git a/Spanner/tests/Unit/DurationTest.php b/Spanner/tests/Unit/DurationTest.php deleted file mode 100644 index 22eb0495f947..000000000000 --- a/Spanner/tests/Unit/DurationTest.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit; - -use Google\Cloud\Spanner\Duration; -use PHPUnit\Framework\TestCase; - -/** - * @group spanner - */ -class DurationTest extends TestCase -{ - const SECONDS = 10; - const NANOS = 1; - - private $duration; - - public function setUp(): void - { - $this->duration = new Duration(self::SECONDS, self::NANOS); - } - - public function testGet() - { - $this->assertEquals([ - 'seconds' => self::SECONDS, - 'nanos' => self::NANOS - ], $this->duration->get()); - } - - public function testType() - { - $this->assertEquals(Duration::TYPE, $this->duration->type()); - } - - public function testFormatAsString() - { - $this->assertEquals( - json_encode($this->duration->get()), - $this->duration->formatAsString() - ); - } - - public function testTostring() - { - $this->assertEquals( - json_encode($this->duration->get()), - (string)$this->duration - ); - } -} diff --git a/Spanner/tests/Unit/Fixtures.php b/Spanner/tests/Unit/Fixtures.php index 44a9d28eb2bb..20b74b87b727 100644 --- a/Spanner/tests/Unit/Fixtures.php +++ b/Spanner/tests/Unit/Fixtures.php @@ -25,11 +25,6 @@ public static function STREAMING_READ_ACCEPTANCE_FIXTURE() return __DIR__ . '/fixtures/streaming-read-acceptance-test.json'; } - public static function INSTANCE_FIXTURE() - { - return __DIR__ . '/fixtures/instance.json'; - } - public static function INSTANCE_CONFIG_FIXTURE() { return __DIR__ . '/fixtures/instanceConfig.json'; diff --git a/Spanner/tests/Unit/GapicBackoff/GapicBackoffTest.php b/Spanner/tests/Unit/GapicBackoff/GapicBackoffTest.php deleted file mode 100644 index 1d22aa87a908..000000000000 --- a/Spanner/tests/Unit/GapicBackoff/GapicBackoffTest.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php -/** - * Copyright 2020 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\GapicBackoff; - -use PHPUnit\Framework\TestCase; -use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Core\Testing\GrpcTestTrait; - -/** - * @group spanner - * @group spanner-gapic-backoff - */ -class GapicBackoffTest extends TestCase -{ - use GrpcTestTrait; - - public function setUp(): void - { - $this->checkAndSkipGrpcTests(); - } - - /** - * @param array $config - * @return \Google\Cloud\Core\GrpcRequestWrapper - */ - private function getWrapper($config) - { - $config += [ - 'apiEndpoint' => '127.0.0.1:10', - 'hasEmulator' => true, - ]; - - $spanner = new SpannerClient($config); - $connection = $spanner->instance('nonexistent')->database('nonexistent')->connection(); - return $connection->requestWrapper(); - } - - public function provideDisabledBackoffConfigs() - { - return [ - [[]], - [['useDiscreteBackoffs' => false]], - ]; - } - - /** - * @dataProvider provideDisabledBackoffConfigs - * @param array $config - */ - public function testBackoffDisabledByDefault($config) - { - $wrapper = $this->getWrapper($config); - $handler = function () { - $args = func_get_args(); - return new MockOperationResponse('test', null, array_pop($args)); - }; - $response = $wrapper->send($handler, [[]]); - $expected = [ - 'retriesEnabled' => false, - ]; - $this->assertEquals($expected, $response->options['retrySettings']); - } - - public function testBackoffEnabledManually() - { - $config = [ - 'useDiscreteBackoffs' => true, - ]; - $wrapper = $this->getWrapper($config); - $handler = function () { - $args = func_get_args(); - return new MockOperationResponse('test', null, array_pop($args)); - }; - $response = $wrapper->send($handler, [[]]); - $expected = []; - $this->assertEquals($expected, $response->options['retrySettings']); - } - - public function testUserConfigsAreNotRuined() - { - $retrySettings = [ - 'retriesEnabled' => false, - 'noRetriesRpcTimeoutMillis' => 1234, - ]; - $config = [ - 'useDiscreteBackoffs' => true, - 'grpcOptions' => [ - 'retrySettings' => $retrySettings, - ], - ]; - $wrapper = $this->getWrapper($config); - $handler = function () { - $args = func_get_args(); - return new MockOperationResponse('test', null, array_pop($args)); - }; - $response = $wrapper->send($handler, [[]]); - $this->assertEquals($retrySettings, $response->options['retrySettings']); - } -} diff --git a/Spanner/tests/Unit/GapicBackoff/MockOperationResponse.php b/Spanner/tests/Unit/GapicBackoff/MockOperationResponse.php deleted file mode 100644 index 156428c89863..000000000000 --- a/Spanner/tests/Unit/GapicBackoff/MockOperationResponse.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright 2020 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\GapicBackoff; - -use \Google\ApiCore\OperationResponse; - -class MockOperationResponse extends OperationResponse -{ - public $options; - - public function __construct($operationName, $operationsClient, $options = []) - { - $this->options = $options; - parent::__construct($operationName, $operationsClient, $options); - } -} diff --git a/Spanner/tests/Unit/InstanceConfigurationTest.php b/Spanner/tests/Unit/InstanceConfigurationTest.php index 944e8bf99291..854b143442d0 100644 --- a/Spanner/tests/Unit/InstanceConfigurationTest.php +++ b/Spanner/tests/Unit/InstanceConfigurationTest.php @@ -17,13 +17,19 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; +use Google\ApiCore\ApiException; +use Google\ApiCore\OperationResponse; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\InstanceConfiguration; -use Google\Cloud\Spanner\Tests\StubCreationTrait; +use Google\LongRunning\Operation; +use Google\Protobuf\Any; +use Google\Rpc\Code; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -36,183 +42,231 @@ class InstanceConfigurationTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT_ID = 'test-project'; const NAME = 'test-config'; - private $connection; - private $configuration; + private $instanceAdminClient; + private Serializer $serializer; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->configuration = TestHelpers::stub(InstanceConfiguration::class, [ - $this->connection->reveal(), - self::PROJECT_ID, - self::NAME, - [], - $this->prophesize(LongRunningConnectionInterface::class)->reveal() - ]); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->serializer = new Serializer(); } public function testName() { + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); + $this->assertEquals( - InstanceAdminClient::parseName($this->configuration->name())['instance_config'], + InstanceAdminClient::parseName($instanceConfig->name())['instance_config'], self::NAME ); } public function testInfo() { - $this->connection->getInstanceConfig(Argument::any())->shouldNotBeCalled(); - $this->configuration->___setProperty('connection', $this->connection->reveal()); - $info = ['foo' => 'bar']; - $config = TestHelpers::stub(InstanceConfiguration::class, [ - $this->connection->reveal(), + $this->instanceAdminClient->getInstanceConfig(Argument::cetera()) + ->shouldNotBeCalled(); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, self::PROJECT_ID, self::NAME, - $info, - $this->prophesize(LongRunningConnectionInterface::class)->reveal() - ]); + $info + ); - $this->assertEquals($info, $config->info()); + $this->assertEquals($info, $instanceConfig->info()); } public function testInfoWithReload() { - $info = ['foo' => 'bar']; - - $this->connection->getInstanceConfig([ - 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, self::NAME), - 'projectName' => InstanceAdminClient::projectName(self::PROJECT_ID) - ])->shouldBeCalled()->willReturn($info); - - $this->configuration->___setProperty('connection', $this->connection->reveal()); + $expected = ['display_name' => 'foo']; + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig($expected)); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); + $info = $instanceConfig->info(); - $this->assertEquals($info, $this->configuration->info()); + $this->assertArrayHasKey('displayName', $info); + $this->assertEquals($expected['display_name'], $info['displayName']); } public function testExists() { - $this->connection->getInstanceConfig(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->configuration->name()) - )) - ->shouldBeCalled() - ->willReturn([]); - $this->configuration->___setProperty('connection', $this->connection->reveal()); - - $this->assertTrue($this->configuration->exists()); + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig()); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); + $this->assertTrue($instanceConfig->exists()); } public function testExistsDoesntExist() { - $this->connection->getInstanceConfig(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->configuration->name()) - )) - ->shouldBeCalled() - ->willThrow(new NotFoundException('', 404)); - $this->configuration->___setProperty('connection', $this->connection->reveal()); - - $this->assertFalse($this->configuration->exists()); + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->will(function () { + throw new ApiException('', Code::NOT_FOUND); + }); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); + + $this->assertFalse($instanceConfig->exists()); } public function testReload() { - $info = ['foo' => 'bar']; - - $this->connection->getInstanceConfig([ - 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, self::NAME), - 'projectName' => InstanceAdminClient::projectName(self::PROJECT_ID) - ])->shouldBeCalledTimes(1)->willReturn($info); - - $this->configuration->___setProperty('connection', $this->connection->reveal()); - - $info = $this->configuration->reload(); + $expected1 = ['some' => 'info']; + $expected2 = ['display_name' => 'bar']; + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig($expected2)); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME, + $expected1 + ); - $info2 = $this->configuration->info(); + $info1 = $instanceConfig->info(); + $info2 = $instanceConfig->reload(); + $info3 = $instanceConfig->info(); - $this->assertEquals($info, $info2); + $this->assertEquals($expected1, $info1); + $this->assertNotEquals($info1, $info2); + $this->assertArrayHasKey('displayName', $info2); + $this->assertEquals($expected2['display_name'], $info2['displayName']); + $this->assertEquals($info2, $info3); } public function testUpdate() { - $config = $this->getDefaultInstance(); - - $this->connection->updateInstanceConfig([ - 'name' => $config['name'], - 'displayName' => 'bar', - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' + $expectedInstanceConfig = new InstanceConfig([ + 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, 'foo'), + 'display_name' => 'bar2' ]); - - $this->configuration->___setProperty('connection', $this->connection->reveal()); - - $this->configuration->update(['displayName' => 'bar']); - } - - public function testUpdateWithExistingLabels() - { - $config = $this->getDefaultInstance(); - $config['labels'] = ['foo' => 'bar']; - - $this->connection->updateInstanceConfig([ - 'labels' => $config['labels'], - 'name' => $config['name'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' + $any = $this->prophesize(Any::class); + $any->getValue()->willReturn($expectedInstanceConfig->serializeToString()); + $operation = $this->prophesize(Operation::class); + $operation->getResponse()->willReturn($any->reveal()); + $operation->getDone()->willReturn(true); + $operationClient = $this->prophesize(\Google\LongRunning\Client\OperationsClient::class); + $operationResponse = new OperationResponse('operation-name', $operationClient->reveal(), [ + 'operationReturnType' => InstanceConfig::class, + 'lastProtoResponse' => $operation->reveal(), ]); - $this->configuration->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->updateInstanceConfig( + Argument::that(function (UpdateInstanceConfigRequest $request) use ($expectedInstanceConfig) { + $instanceConfig = $request->getInstanceConfig(); + return $instanceConfig->getDisplayName() === $expectedInstanceConfig->getDisplayName() + && $instanceConfig->getName() === $expectedInstanceConfig->getName(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($operationResponse); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + 'foo', + ); + + $operation = $instanceConfig->update(['displayName' => 'bar2']); + $operation->pollUntilComplete(); + $updatedInstanceConfig = $operation->getResult(); - $this->configuration->update(['labels' => $config['labels']]); + $info = $updatedInstanceConfig->info(); + $this->assertEquals('bar2', $info['displayName']); } public function testUpdateWithChanges() { - $config = $this->getDefaultInstance(); - - $changes = [ - 'labels' => [ - 'foo' => 'bar' - ], + $config = [ + 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, self::NAME), + 'labels' => ['foo' => 'bar'], 'displayName' => 'New Name', ]; - $this->connection->updateInstanceConfig([ - 'name' => $config['name'], - 'displayName' => $changes['displayName'], - 'labels' => $changes['labels'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); - - $this->configuration->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->updateInstanceConfig( + Argument::that(function (UpdateInstanceConfigRequest $request) use ($config) { + $instanceConfig = $request->getInstanceConfig()->serializeToJsonString(); + return json_decode($instanceConfig, true) == $config; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); - $this->configuration->update($changes); + $instanceConfig->update(['displayName' => 'New Name', 'labels' => ['foo' => 'bar']]); } public function testDelete() { - $this->connection->deleteInstanceConfig([ - 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, self::NAME) - ])->shouldBeCalled(); - - $this->configuration->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->deleteInstanceConfig( + Argument::type(DeleteInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); + + $instanceConfig = new InstanceConfiguration( + $this->instanceAdminClient->reveal(), + $this->serializer, + self::PROJECT_ID, + self::NAME + ); - $this->configuration->delete(); + $instanceConfig->delete(); } private function getDefaultInstance() diff --git a/Spanner/tests/Unit/InstanceTest.php b/Spanner/tests/Unit/InstanceTest.php index cb0564d92e0e..41ee2ed4d730 100644 --- a/Spanner/tests/Unit/InstanceTest.php +++ b/Spanner/tests/Unit/InstanceTest.php @@ -17,26 +17,41 @@ namespace Google\Cloud\Spanner\Tests\Unit; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\Iam\Iam; +use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesResponse; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; +use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; -use Google\Cloud\Spanner\Tests\StubCreationTrait; -use Google\Cloud\Spanner\Backup; +use Google\Cloud\Spanner\KeySet; +use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\Session; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\Operation; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Spanner\Result; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; /** * @group spanner @@ -47,8 +62,6 @@ class InstanceTest extends TestCase use GrpcTestTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; - use ResultGeneratorTrait; const PROJECT_ID = 'test-project'; const NAME = 'instance-name'; @@ -56,38 +69,51 @@ class InstanceTest extends TestCase const BACKUP = 'my-backup'; const SESSION = 'projects/test-project/instances/instance-name/databases/database-name/sessions/session'; - private $connection; - private $instance; - private $lroConnection; private $directedReadOptionsIncludeReplicas; + private $instance; + private $serializer; + private $spannerClient; + private $instanceAdminClient; + private $databaseAdminClient; + private $operationResponse; + private $page; + private $pagedListResponse; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); $this->directedReadOptionsIncludeReplicas = [ 'includeReplicas' => [ - 'replicaSelections' => [ + 'replicaSelections' => [[ 'location' => 'us-central1', - 'type' => 'READ_WRITE', - 'autoFailoverDisabled' => false - ] + 'type' => Type::READ_WRITE, + ]], 'autoFailoverDisabled' => false ] ]; - $this->instance = TestHelpers::stub(Instance::class, [ - $this->connection->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + + $this->page = $this->prophesize(Page::class); + $this->pagedListResponse = $this->prophesize(PagedListResponse::class); + $this->pagedListResponse->getPage()->willReturn($this->page->reveal()); + + $this->instance = new Instance( + $this->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, self::PROJECT_ID, self::NAME, false, [], ['directedReadOptions' => $this->directedReadOptionsIncludeReplicas] - ], [ - 'info', - 'connection' - ]); + ); } public function testName() @@ -97,50 +123,39 @@ public function testName() public function testInfo() { - $this->connection->getInstance(Argument::any())->shouldNotBeCalled(); - - $this->instance->___setProperty('info', ['foo' => 'bar']); - $this->assertEquals('bar', $this->instance->info()['foo']); - } - - public function testInfoWithReload() - { - $instance = $this->getDefaultInstance(); - - $this->connection->getInstance(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->instance->name()) - )) - ->shouldBeCalledTimes(1) - ->willReturn($instance); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return $message['name'] == $this->instance->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->getDefaultInstance()); $info = $this->instance->info(); $this->assertEquals('Instance Name', $info['displayName']); + // test calling info again does not reload $this->assertEquals($info, $this->instance->info()); } public function testInfoWithReloadAndFieldMask() { - $instance = [ - 'name' => $this->instance->name(), - 'node_count' => 1 - ]; - - $requestedFieldNames = ["name", 'node_count']; - $this->connection->getInstance(Argument::allOf( - Argument::withEntry('name', $this->instance->name()), - Argument::withEntry('fieldMask', $requestedFieldNames) - )) - ->shouldBeCalledTimes(1) - ->willReturn($instance); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $requestedFieldNames = ['name', 'node_count']; + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) use ($requestedFieldNames) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals(['paths' => $requestedFieldNames], $message['fieldMask']); + return $message['name'] == $this->instance->name(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceProto([ + 'name' => $this->instance->name(), + 'node_count' => 1 + ])); $info = $this->instance->info(['fieldMask' => $requestedFieldNames]); @@ -149,32 +164,30 @@ public function testInfoWithReloadAndFieldMask() public function testExists() { - $this->connection->getInstance(Argument::allOf( - Argument::withEntry('name', $this->instance->name()), - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('fieldMask', ['name']) - )) - ->shouldBeCalledTimes(1) - ->willReturn([]); - - $this->connection->getInstance(Argument::allOf( - Argument::withEntry('name', $this->instance->name()), - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::not(Argument::withKey('fieldMask')) - )) + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return isset($message['fieldMask']) && ['paths' => ['name']] == $message['fieldMask']; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceProto()); + + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return !isset($message['fieldMask']); + }), + Argument::type('array') + ) ->shouldBeCalledTimes(2) - ->willReturn([ + ->willReturn(new InstanceProto([ 'name' => $this->instance->name(), - 'nodeCount' => 1, - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + 'node_count' => 1, + ])); $this->assertTrue($this->instance->exists()); @@ -185,36 +198,32 @@ public function testExists() public function testExistsNotFound() { - $this->connection->getInstance(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->instance->name()) - )) - ->shouldBeCalled() + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willThrow(new NotFoundException('foo', 404)); - $this->instance->___setProperty('connection', $this->connection->reveal()); - $this->assertFalse($this->instance->exists()); } public function testReload() { - $instance = $this->getDefaultInstance(); - - $this->connection->getInstance(Argument::allOf( - Argument::withEntry('name', $this->instance->name()), - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ) - )) - ->shouldBeCalledTimes(1) - ->willReturn($instance); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->getDefaultInstance()); $info = $this->instance->reload(); @@ -223,24 +232,20 @@ public function testReload() public function testReloadWithFieldMask() { - $instance = [ - 'name' => $this->instance->name(), - 'node_count' => 1 - ]; - - $requestedFieldNames = ["name", 'node_count']; - $this->connection->getInstance(Argument::allOf( - Argument::withEntry('name', $this->instance->name()), - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('fieldMask', $requestedFieldNames) - )) - ->shouldBeCalledTimes(1) - ->willReturn($instance); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $requestedFieldNames = ['name', 'node_count']; + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) use ($requestedFieldNames) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return $message['fieldMask'] == ['paths' => $requestedFieldNames]; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceProto([ + 'name' => $this->instance->name(), + 'node_count' => 1 + ])); $info = $this->instance->reload(['fieldMask' => $requestedFieldNames]); @@ -249,68 +254,65 @@ public function testReloadWithFieldMask() public function testState() { - $instance = $this->getDefaultInstance(); - - $this->connection->getInstance(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->instance->name()) - )) - ->shouldBeCalledTimes(1) - ->willReturn($instance); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->getDefaultInstance()); $this->assertEquals(Instance::STATE_READY, $this->instance->state()); } public function testStateIsNull() { - $this->connection->getInstance(Argument::allOf( - Argument::withEntry( - 'projectName', - InstanceAdminClient::projectName(self::PROJECT_ID) - ), - Argument::withEntry('name', $this->instance->name()) - )) - ->shouldBeCalledTimes(1) - ->willReturn([]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->getInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['name'], $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceProto()); $this->assertNull($this->instance->state()); } public function testUpdate() { - $instance = $this->getDefaultInstance(); - - $this->connection->updateInstance([ - 'displayName' => 'bar', - 'name' => $instance['name'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->updateInstance( + Argument::that(function ($request) { + $this->assertEquals('bar', $request->getInstance()->getDisplayName()); + $this->assertEquals($this->instance->name(), $request->getInstance()->getName()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $this->instance->update(['displayName' => 'bar']); } public function testUpdateWithProcessingUnits() { - $instance = $this->getDefaultInstance(); - - $this->connection->updateInstance([ - 'processingUnits' => 500, - 'name' => $instance['name'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->updateInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals(500, $request->getInstance()->getProcessingUnits()); + $this->assertEquals($this->instance->name(), $request->getInstance()->getName()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $this->instance->update(['processingUnits' => 500]); } @@ -324,54 +326,55 @@ public function testUpdateRaisesInvalidArgument() public function testUpdateWithExistingLabels() { - $instance = $this->getDefaultInstance(); - $instance['labels'] = ['foo' => 'bar']; - - $this->connection->updateInstance([ - 'labels' => $instance['labels'], - 'name' => $instance['name'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' - ]); + $this->instanceAdminClient->updateInstance( + Argument::that(function ($request) { + $this->assertEquals(['foo' => 'bar'], iterator_to_array($request->getInstance()->getLabels())); + $this->assertEquals($this->instance->name(), $request->getInstance()->getName()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); - - $this->instance->update(['labels' => $instance['labels']]); + $this->instance->update(['labels' => ['foo' => 'bar']]); } public function testUpdateWithChanges() { - $instance = $this->getDefaultInstance(); - - $changes = [ + $this->instanceAdminClient->updateInstance( + Argument::that(function ($request) { + $this->assertEquals('New Name', $request->getInstance()->getDisplayName()); + $this->assertEquals(['foo' => 'bar'], iterator_to_array($request->getInstance()->getLabels())); + $this->assertEquals(900, $request->getInstance()->getNodeCount()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); + + $this->instance->update([ 'labels' => [ 'foo' => 'bar' ], 'nodeCount' => 900, 'displayName' => 'New Name', - ]; - - $this->connection->updateInstance([ - 'name' => $instance['name'], - 'displayName' => $changes['displayName'], - 'nodeCount' => $changes['nodeCount'], - 'labels' => $changes['labels'], - ])->shouldBeCalled()->willReturn([ - 'name' => 'my-operation' ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); - - $this->instance->update($changes); } public function testDelete() { - $this->connection->deleteInstance([ - 'name' => InstanceAdminClient::instanceName(self::PROJECT_ID, self::NAME) - ])->shouldBeCalled(); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->instanceAdminClient->deleteInstance( + Argument::that(function ($request) { + $this->assertEquals( + $request->getName(), + InstanceAdminClient::instanceName(self::PROJECT_ID, self::NAME) + ); + return true; + }), + Argument::type('array') + )->shouldBeCalledOnce(); $this->instance->delete(); } @@ -380,57 +383,68 @@ public function testCreateDatabase() { $extra = ['foo', 'bar']; - $this->connection->createDatabase([ - 'instance' => InstanceAdminClient::instanceName(self::PROJECT_ID, self::NAME), - 'createStatement' => 'CREATE DATABASE `test-database`', - 'extraStatements' => $extra - ]) - ->shouldBeCalled() - ->willReturn(['name' => 'operations/foo']); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->createDatabase( + Argument::that(function ($request) use ($extra) { + $createStatement = 'CREATE DATABASE `test-database`'; + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['createStatement'], $createStatement); + $this->assertEquals( + $message['parent'], + InstanceAdminClient::instanceName(self::PROJECT_ID, self::NAME) + ); + $this->assertEquals($message['extraStatements'], $extra); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $database = $this->instance->createDatabase('test-database', [ 'statements' => $extra ]); - $this->assertInstanceOf(LongRunningOperation::class, $database); + $this->assertInstanceOf(OperationResponse::class, $database); } public function testCreateDatabaseFromBackupName() { $backupName = DatabaseAdminClient::backupName(self::PROJECT_ID, self::NAME, self::BACKUP); - $this->connection->restoreDatabase(Argument::allOf( - Argument::withEntry('databaseId', 'restore-database'), - Argument::withEntry('instance', $this->instance->name()), - Argument::withEntry('backup', $backupName) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - $this->instance->___setProperty('connection', $this->connection->reveal()); + + $this->databaseAdminClient->restoreDatabase( + Argument::that(function ($request) use ($backupName) { + $this->assertEquals($request->getDatabaseId(), 'restore-database'); + $this->assertEquals($request->getParent(), $this->instance->name()); + $this->assertEquals($request->getBackup(), $backupName); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->instance->createDatabaseFromBackup('restore-database', $backupName); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } public function testCreateDatabaseFromBackupObject() { $backupObject = $this->instance->backup(self::BACKUP); - $this->connection->restoreDatabase(Argument::allOf( - Argument::withEntry('databaseId', 'restore-database'), - Argument::withEntry('instance', $this->instance->name()), - Argument::withEntry('backup', $backupObject->name()) - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); - $this->instance->___setProperty('connection', $this->connection->reveal()); + + $this->databaseAdminClient->restoreDatabase( + Argument::that(function ($request) use ($backupObject) { + $this->assertEquals($request->getDatabaseId(), 'restore-database'); + $this->assertEquals($request->getParent(), $this->instance->name()); + $this->assertEquals($request->getBackup(), $backupObject->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); $op = $this->instance->createDatabaseFromBackup('restore-database', $backupObject); - $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertInstanceOf(OperationResponse::class, $op); } public function testDatabase() @@ -443,17 +457,23 @@ public function testDatabase() public function testDatabases() { $databases = [ - ['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database1')], - ['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database2')] + new DatabaseProto(['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database1')]), + new DatabaseProto(['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database2')]) ]; - $this->connection->listDatabases(Argument::withEntry('instance', $this->instance->name())) - ->shouldBeCalled() - ->willReturn(['databases' => $databases]); + $this->page->getResponseObject()->willReturn(new ListDatabasesResponse(['databases' => $databases])); - $this->connection->getDatabase(Argument::any())->shouldNotBeCalled(); + $this->databaseAdminClient->listDatabases( + Argument::that(function ($request) { + $this->assertEquals($request->getParent(), $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getDatabase(Argument::cetera())->shouldNotBeCalled(); $dbs = $this->instance->databases(); @@ -466,23 +486,59 @@ public function testDatabases() $this->assertEquals('database2', DatabaseAdminClient::parseName($dbs[1]->name())['database']); // Make sure the database->info is prefilled. - $this->assertEquals($databases[0], $dbs[0]->info()); - $this->assertEquals($databases[1], $dbs[1]->info()); + $this->assertEquals($databases[0]->__debugInfo(), array_filter($dbs[0]->info())); + $this->assertEquals($databases[1]->__debugInfo(), array_filter($dbs[1]->info())); } public function testDatabasesPaged() { $databases = [ - ['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database1')], - ['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database2')] + new DatabaseProto(['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database1')]), + new DatabaseProto(['name' => DatabaseAdminClient::databaseName(self::PROJECT_ID, self::NAME, 'database2')]), ]; - $iteration = 0; - $this->connection->listDatabases(Argument::withEntry('instance', $this->instance->name())) - ->shouldBeCalledTimes(2) - ->willReturn(['databases' => [$databases[0]], 'nextPageToken' => 'foo'], ['databases' => [$databases[1]]]); + $page1 = $this->prophesize(Page::class); + $page1->getResponseObject() + ->willReturn(new ListDatabasesResponse([ + 'databases' => [$databases[0]], 'next_page_token' => 'foo' + ])); + $pagedListResponse1 = $this->prophesize(PagedListResponse::class); + $pagedListResponse1->getPage() + ->willReturn($page1->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $iteration = 0; + $this->databaseAdminClient->listDatabases( + Argument::that(function ($request) use (&$iteration) { + $this->assertEquals($request->getParent(), $this->instance->name()); + $iteration++; + return $iteration == 1; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($pagedListResponse1->reveal()); + + $page2 = $this->prophesize(Page::class); + $page2->getResponseObject() + ->willReturn(new ListDatabasesResponse(['databases' => [$databases[1]]])); + $pagedListResponse2 = $this->prophesize(PagedListResponse::class); + $pagedListResponse2->getPage() + ->willReturn($page2->reveal()); + + $this->databaseAdminClient->listDatabases( + Argument::that(function ($request) use (&$iteration) { + $this->assertEquals($request->getParent(), $this->instance->name()); + if ($iteration == 2) { + $this->assertEquals($request->getPageToken(), 'foo'); + return true; + } + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($pagedListResponse2->reveal()); + + $this->databaseAdminClient->getDatabase(Argument::cetera())->shouldNotBeCalled(); $dbs = $this->instance->databases(); @@ -497,7 +553,7 @@ public function testDatabasesPaged() public function testIam() { - $this->assertInstanceOf(Iam::class, $this->instance->iam()); + $this->assertInstanceOf(IamManager::class, $this->instance->iam()); } public function testBackup() @@ -513,19 +569,21 @@ public function testBackup() public function testBackups() { $backups = [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::NAME, 'backup1'), - ], - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::NAME, 'backup2'), - ] + new BackupProto(['name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::NAME, 'backup1')]), + new BackupProto(['name' => DatabaseAdminClient::backupName(self::PROJECT_ID, self::NAME, 'backup2')]), ]; - $this->connection->listBackups(Argument::withEntry('instance', $this->instance->name())) - ->shouldBeCalled() - ->willReturn(['backups' => $backups]); + $this->page->getResponseObject()->willReturn(new ListBackupsResponse(['backups' => $backups])); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->listBackups( + Argument::that(function ($request) { + $this->assertEquals($request->getParent(), $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); $bkps = $this->instance->backups(); @@ -541,15 +599,26 @@ public function testBackups() public function testBackupOperations() { $operations = [ - ['name' => 'operation1'], - ['name' => 'operation2'] + new Operation(['name' => 'operation1']), + new Operation(['name' => 'operation2']), ]; - $this->connection->listBackupOperations(Argument::withEntry('instance', $this->instance->name())) - ->shouldBeCalled() - ->willReturn(['operations' => $operations]); + $this->page->getResponseObject()->willReturn(new ListBackupOperationsResponse(['operations' => $operations])); + $this->page->getNextPageToken()->willReturn(null); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->listBackupOperations( + Argument::that(function ($request) { + $this->assertEquals($request->getParent(), $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); + + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledTimes(2) + ->willReturn($this->prophesize(OperationsClient::class)->reveal()); $bkpOps = $this->instance->backupOperations(); @@ -557,22 +626,33 @@ public function testBackupOperations() $bkpOps = iterator_to_array($bkpOps); $this->assertCount(2, $bkpOps); - $this->assertEquals('operation1', $bkpOps[0]->name()); - $this->assertEquals('operation2', $bkpOps[1]->name()); + $this->assertEquals('operation1', $bkpOps[0]->getName()); + $this->assertEquals('operation2', $bkpOps[1]->getName()); } public function testListDatabaseOperations() { $operations = [ - ['name' => 'operation1'], - ['name' => 'operation2'] + new Operation(['name' => 'operation1']), + new Operation(['name' => 'operation2']), ]; - $this->connection->listDatabaseOperations(Argument::withEntry('instance', $this->instance->name())) - ->shouldBeCalled() - ->willReturn(['operations' => $operations]); + $this->page->getResponseObject()->willReturn(new ListDatabaseOperationsResponse(['operations' => $operations])); + $this->page->getNextPageToken()->willReturn(null); + + $this->databaseAdminClient->listDatabaseOperations( + Argument::that(function ($request) { + $this->assertEquals($request->getParent(), $this->instance->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledTimes(2) + ->willReturn($this->prophesize(OperationsClient::class)->reveal()); $dbOps = $this->instance->databaseOperations(); @@ -580,8 +660,8 @@ public function testListDatabaseOperations() $dbOps = iterator_to_array($dbOps); $this->assertCount(2, $dbOps); - $this->assertEquals('operation1', $dbOps[0]->name()); - $this->assertEquals('operation2', $dbOps[1]->name()); + $this->assertEquals('operation1', $dbOps[0]->getName()); + $this->assertEquals('operation2', $dbOps[1]->getName()); } public function testInstanceDatabaseRole() @@ -589,16 +669,29 @@ public function testInstanceDatabaseRole() $sql = 'SELECT * FROM Table'; $database = $this->instance->database($this::DATABASE, ['databaseRole' => 'Reader']); - $this->connection->createSession(Argument::withEntry( - 'session', - ['labels' => [], 'creator_role' => 'Reader'] - )) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - $this->connection->executeStreamingSql(Argument::withEntry('sql', $sql)) - ->shouldBeCalled()->willReturn($this->resultGenerator()); + $this->spannerClient->createSession( + Argument::that(function ($request) { + return $this->serializer->encodeMessage($request)['session']['creatorRole'] + == 'Reader'; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Session(['name' => self::SESSION])); + + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + return $request->getSql() == $sql; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); + + $this->spannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + )->shouldBeCalledOnce(); $database->execute($sql); } @@ -608,20 +701,31 @@ public function testInstanceExecuteWithDirectedRead() $database = $this->instance->database( $this::DATABASE ); - $this->connection->createSession(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - - $this->connection->executeStreamingSql(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsIncludeReplicas - )) - ->shouldBeCalled() - ->willReturn( - $this->resultGenerator() - ); + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Session(['name' => self::SESSION])); + + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + $this->directedReadOptionsIncludeReplicas + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); + + $this->spannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + )->shouldBeCalledOnce(); $sql = 'SELECT * FROM Table'; $res = $database->execute($sql); @@ -635,23 +739,33 @@ public function testInstanceReadWithDirectedRead() $table = 'foo'; $keys = [10, 'bar']; $columns = ['id', 'name']; - $database = $this->instance->database( - $this::DATABASE, - ); - $this->connection->createSession(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - - $this->connection->streamingRead(Argument::withEntry( - 'directedReadOptions', - $this->directedReadOptionsIncludeReplicas - )) - ->shouldBeCalled() - ->willReturn( - $this->resultGenerator() - ); + $database = $this->instance->database($this::DATABASE); + + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new Session(['name' => self::SESSION])); + + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['directedReadOptions'], + $this->directedReadOptionsIncludeReplicas + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); + + $this->spannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + )->shouldBeCalledOnce(); $res = $database->read( $table, @@ -667,6 +781,12 @@ public function testInstanceReadWithDirectedRead() private function getDefaultInstance() { - return json_decode(file_get_contents(Fixtures::INSTANCE_FIXTURE()), true); + return new InstanceProto([ + 'name' => 'projects/test-project/instances/instance-name', + 'config' => 'projects/test-project/instanceConfigs/regional-europe-west1', + 'display_name' => 'Instance Name', + 'node_count' => 1, + 'state' => 2 + ]); } } diff --git a/Spanner/tests/Unit/KeyRangeTest.php b/Spanner/tests/Unit/KeyRangeTest.php index a4da57680852..0082a040de16 100644 --- a/Spanner/tests/Unit/KeyRangeTest.php +++ b/Spanner/tests/Unit/KeyRangeTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\KeyRange; use InvalidArgumentException; use PHPUnit\Framework\TestCase; @@ -35,7 +35,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->range = new KeyRange; + $this->range = new KeyRange(); } public function testConstructWithScalars() diff --git a/Spanner/tests/Unit/KeySetTest.php b/Spanner/tests/Unit/KeySetTest.php index 8b2c772b7840..05f68e89b5bd 100644 --- a/Spanner/tests/Unit/KeySetTest.php +++ b/Spanner/tests/Unit/KeySetTest.php @@ -32,7 +32,7 @@ class KeySetTest extends TestCase public function testAddRange() { - $set = new KeySet; + $set = new KeySet(); $range = $this->prophesize(KeyRange::class); $range->keyRangeObject()->willReturn('foo'); @@ -43,7 +43,7 @@ public function testAddRange() public function testSetRanges() { - $set = new KeySet; + $set = new KeySet(); $range1 = $this->prophesize(KeyRange::class); $range1->keyRangeObject()->willReturn('foo'); @@ -64,7 +64,7 @@ public function testSetRanges() public function testAddKey() { - $set = new KeySet; + $set = new KeySet(); $key = 'key'; @@ -75,9 +75,9 @@ public function testAddKey() public function testSetKeys() { - $set = new KeySet; + $set = new KeySet(); - $keys = ['key1','key2']; + $keys = ['key1', 'key2']; $set->setKeys($keys); @@ -86,7 +86,7 @@ public function testSetKeys() public function testSetMatchAll() { - $set = new KeySet; + $set = new KeySet(); $set->setMatchAll(true); $this->assertTrue($set->keySetObject()['all']); @@ -97,7 +97,7 @@ public function testSetMatchAll() public function testRanges() { - $set = new KeySet; + $set = new KeySet(); $range = $this->prophesize(KeyRange::class)->reveal(); $set->addRange($range); @@ -106,7 +106,7 @@ public function testRanges() public function testKeys() { - $set = new KeySet; + $set = new KeySet(); $key = 'foo'; $set->addKey($key); @@ -139,7 +139,7 @@ public function testInvalidAll() public function testFromArray() { $range = new KeyRange(['start' => 'foo', 'end' => 'bar']); - $keys = ['a','b']; + $keys = ['a', 'b']; $all = true; $res = (new KeySet([ 'keys' => $keys, diff --git a/Spanner/tests/Unit/OperationTest.php b/Spanner/tests/Unit/OperationTest.php index bfe2c23dfd4c..ec7d0d861131 100644 --- a/Spanner/tests/Unit/OperationTest.php +++ b/Spanner/tests/Unit/OperationTest.php @@ -18,14 +18,12 @@ namespace Google\Cloud\Spanner\Tests\Unit; use Google\ApiCore\ServerStream; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Batch\ReadPartition; -use Google\Cloud\Spanner\Connection\Grpc; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; @@ -33,15 +31,26 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\CommitResponse\CommitStats; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\PartitionResponse; use Google\Cloud\Spanner\V1\ResultSet; +use Google\Cloud\Spanner\V1\ResultSetMetadata; use Google\Cloud\Spanner\V1\ResultSetStats; -use Google\Cloud\Spanner\V1\SpannerClient; +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; +use Google\Cloud\Spanner\V1\Type; +use Google\Protobuf\Duration; +use Google\Protobuf\Timestamp as TimestampProto; +use Google\Protobuf\Value; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -53,7 +62,7 @@ class OperationTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; + use ApiHelperTrait; const SESSION = 'my-session-id'; const TRANSACTION = 'my-transaction-id'; @@ -61,20 +70,23 @@ class OperationTest extends TestCase const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const TIMESTAMP = '2017-01-09T18:05:22.534799Z'; - private $connection; private $operation; private $session; + private $spannerClient; + private $serializer; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(SpannerClient::class); - $this->operation = TestHelpers::stub(Operation::class, [ - $this->connection->reveal(), + $this->operation = new Operation( + $this->spannerClient->reveal(), + $this->serializer, false - ]); + ); $session = $this->prophesize(Session::class); $session->name()->willReturn(self::SESSION); @@ -119,23 +131,29 @@ public function testDeleteMutation() public function testCommit() { - $mutations = [ - $this->operation->mutation(Operation::OP_INSERT, 'Posts', [ - 'foo' => 'bar' - ]) - ]; - - $this->connection->commit(Argument::allOf( - Argument::withEntry('mutations', $mutations), - Argument::withEntry('transactionId', 'foo') - ))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => self::TIMESTAMP - ]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $mutation = $this->operation->mutation(Operation::OP_INSERT, 'Posts', ['foo' => 'bar']); + + $this->spannerClient->commit( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getMutations()[0]->getInsert()->getTable()); + $this->assertEquals( + $this->serializer->encodeMessage($request->getMutations()[0]->getInsert())['values'], + [['bar']] + ); + $this->assertEquals( + $request->getMutations()[0]->getInsert()->getColumns()[0], + 'foo' + ); + $this->assertEquals(self::TRANSACTION, $request->getTransactionId()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); - $res = $this->operation->commit($this->session, $mutations, [ - 'transactionId' => 'foo' + $res = $this->operation->commit($this->session, [$mutation], [ + 'transactionId' => self::TRANSACTION ]); $this->assertInstanceOf(Timestamp::class, $res); @@ -143,24 +161,23 @@ public function testCommit() public function testCommitWithReturnCommitStats() { - $mutations = [ - $this->operation->mutation(Operation::OP_INSERT, 'Posts', [ - 'foo' => 'bar' - ]) - ]; + $mutation = $this->operation->mutation(Operation::OP_INSERT, 'Posts', ['foo' => 'bar']); - $this->connection->commit(Argument::allOf( - Argument::withEntry('mutations', $mutations), - Argument::withEntry('transactionId', 'foo'), - Argument::withEntry('returnCommitStats', true) - ))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => self::TIMESTAMP, - 'commitStats' => ['mutationCount' => 1] - ]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getMutations()[0]->getInsert()->getTable()); + $this->assertEquals('foo', $request->getTransactionId()); + $this->assertEquals(true, $request->getReturnCommitStats()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse([ + 'commit_stats' => new CommitStats(['mutation_count' => 1]) + ])); - $res = $this->operation->commitWithResponse($this->session, $mutations, [ + $res = $this->operation->commitWithResponse($this->session, [$mutation], [ 'transactionId' => 'foo', 'returnCommitStats' => true ]); @@ -174,24 +191,29 @@ public function testCommitWithReturnCommitStats() public function testCommitWithMaxCommitDelay() { - $duration = new Duration(0, 100000000); - $mutations = [ - $this->operation->mutation(Operation::OP_INSERT, 'Posts', [ - 'foo' => 'bar' - ]) - ]; - - $this->connection->commit(Argument::allOf( - Argument::withEntry('mutations', $mutations), - Argument::withEntry('transactionId', 'foo'), - Argument::withEntry('maxCommitDelay', $duration) - ))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => self::TIMESTAMP, - ]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $duration = new Duration(['seconds' => 0, 'nanos' => 100000000]); + $mutation = $this->operation->mutation(Operation::OP_INSERT, 'Posts', ['foo' => 'bar']); + + $this->spannerClient->commit( + Argument::that(function ($request) use ($duration) { + $this->assertEquals('Posts', $request->getMutations()[0]->getInsert()->getTable()); + $this->assertEquals('foo', $request->getTransactionId()); + $this->assertEquals( + $duration->getSeconds(), + $request->getMaxCommitDelay()->getSeconds() + ); + $this->assertEquals( + $duration->getNanos(), + $request->getMaxCommitDelay()->getNanos() + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); - $res = $this->operation->commitWithResponse($this->session, $mutations, [ + $res = $this->operation->commitWithResponse($this->session, [$mutation], [ 'transactionId' => 'foo', 'maxCommitDelay' => $duration, ]); @@ -204,25 +226,20 @@ public function testCommitWithMaxCommitDelay() public function testCommitWithExistingTransaction() { - $mutations = [ - $this->operation->mutation(Operation::OP_INSERT, 'Posts', [ - 'foo' => 'bar' - ]) - ]; - - $this->connection->commit(Argument::allOf( - Argument::withEntry('mutations', $mutations), - Argument::withEntry('transactionId', self::TRANSACTION), - Argument::that(function ($arg) { - return !isset($arg['singleUseTransaction']); - }) - ))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => self::TIMESTAMP - ]); + $mutation = $this->operation->mutation(Operation::OP_INSERT, 'Posts', ['foo' => 'bar']); - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getMutations()[0]->getInsert()->getTable()); + $this->assertEquals(self::TRANSACTION, $request->getTransactionId()); + return !$request->hasSingleUseTransaction(); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->commitResponse()); - $res = $this->operation->commit($this->session, $mutations, [ + $res = $this->operation->commit($this->session, [$mutation], [ 'transactionId' => self::TRANSACTION ]); @@ -231,12 +248,14 @@ public function testCommitWithExistingTransaction() public function testRollback() { - $this->connection->rollback(Argument::allOf( - Argument::withEntry('transactionId', self::TRANSACTION), - Argument::withEntry('session', self::SESSION) - ))->shouldBeCalled(); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->rollback( + Argument::that(function ($request) { + $this->assertEquals(self::TRANSACTION, $request->getTransactionId()); + $this->assertEquals(self::SESSION, $request->getSession()); + return true; + }), + Argument::type('array') + )->shouldBeCalledOnce(); $this->operation->rollback($this->session, self::TRANSACTION); } @@ -246,16 +265,22 @@ public function testExecute() $sql = 'SELECT * FROM Posts WHERE ID = @id'; $params = ['id' => 10]; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('params', ['id' => '10']), - Argument::that(function ($arg) { - return $arg['paramTypes']['id']['code'] === Database::TYPE_INT64; - }) - ))->shouldBeCalled()->willReturn($this->executeAndReadResponse()); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) use ($sql) { + $data = $this->serializer->encodeMessage($request); + $this->assertEquals($sql, $request->getSql()); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertEquals(['id' => '10'], $data['params']); + $this->assertEquals( + ['id' => ['code' => Database::TYPE_INT64, 'typeAnnotation' => 0, 'protoTypeFqn' => '']], + $data['paramTypes'], + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->executeAndReadResponseStream()); $res = $this->operation->execute($this->session, $sql, [ 'parameters' => $params @@ -268,14 +293,18 @@ public function testExecute() public function testRead() { - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('table', 'Posts'), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('keySet', ['all' => true]), - Argument::withEntry('columns', ['foo']) - ))->shouldBeCalled()->willReturn($this->executeAndReadResponse()); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getTable()); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertTrue($request->getKeySet()->getAll()); + $this->assertEquals(['foo'], $this->serializer->encodeMessage($request)['columns']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->executeAndReadResponseStream()); $res = $this->operation->read($this->session, 'Posts', new KeySet(['all' => true]), ['foo']); $this->assertInstanceOf(Result::class, $res); @@ -285,20 +314,23 @@ public function testRead() public function testReadWithTransaction() { - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('table', 'Posts'), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('keySet', ['all' => true]), - Argument::withEntry('columns', ['foo']) - ))->shouldBeCalled()->willReturn($this->executeAndReadResponse([ - 'transaction' => ['id' => self::TRANSACTION] - ])); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getTable()); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertTrue($request->getKeySet()->getAll()); + $this->assertEquals(['foo'], $this->serializer->encodeMessage($request)['columns']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->executeAndReadResponseStream(self::TRANSACTION)); $res = $this->operation->read($this->session, 'Posts', new KeySet(['all' => true]), ['foo'], [ 'transactionContext' => SessionPoolInterface::CONTEXT_READWRITE ]); + $res->rows()->next(); $this->assertInstanceOf(Transaction::class, $res->transaction()); @@ -307,16 +339,18 @@ public function testReadWithTransaction() public function testReadWithSnapshot() { - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('table', 'Posts'), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('keySet', ['all' => true]), - Argument::withEntry('columns', ['foo']) - ))->shouldBeCalled()->willReturn($this->executeAndReadResponse([ - 'transaction' => ['id' => self::TRANSACTION] - ])); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getTable()); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertTrue($request->getKeySet()->getAll()); + $this->assertEquals(['foo'], $this->serializer->encodeMessage($request)['columns']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->executeAndReadResponseStream(self::TRANSACTION)); $res = $this->operation->read($this->session, 'Posts', new KeySet(['all' => true]), ['foo'], [ 'transactionContext' => SessionPoolInterface::CONTEXT_READ @@ -329,15 +363,15 @@ public function testReadWithSnapshot() public function testTransaction() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('session', $this->session->name()), - Argument::withEntry('requestOptions', ['transactionTag' => self::TRANSACTION_TAG]) - )) - ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $this->assertEquals($request->getSession(), $this->session->name()); + return $request->getRequestOptions()->getTransactionTag() == self::TRANSACTION_TAG; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $t = $this->operation->transaction($this->session, ['tag' => self::TRANSACTION_TAG]); $this->assertInstanceOf(Transaction::class, $t); @@ -346,15 +380,18 @@ public function testTransaction() public function testTransactionNoTag() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('session', $this->session->name()), - Argument::withEntry('requestOptions', []) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + $this->assertEquals($request->getSession(), $this->session->name()); + $this->assertEquals(0, $request->getRequestOptions()->getPriority()); + $this->assertEquals('', $request->getRequestOptions()->getRequestTag()); + $this->assertEquals('', $request->getRequestOptions()->getTransactionTag()); + return true; + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $t = $this->operation->transaction($this->session); $this->assertInstanceOf(Transaction::class, $t); @@ -363,11 +400,9 @@ public function testTransactionNoTag() public function testTransactionWithExcludeTxnFromChangeStreams() { - $gapic = $this->prophesize(SpannerClient::class); - $gapic->beginTransaction( - self::SESSION, - Argument::that(function (TransactionOptions $options) { - $this->assertTrue($options->getExcludeTxnFromChangeStreams()); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertTrue($request->getOptions()->getExcludeTxnFromChangeStreams()); return true; }), Argument::type('array') @@ -375,12 +410,7 @@ public function testTransactionWithExcludeTxnFromChangeStreams() ->shouldBeCalled() ->willReturn(new TransactionProto(['id' => 'foo'])); - $operation = new Operation( - new Grpc(['gapicSpannerClient' => $gapic->reveal()]), - true - ); - - $transaction = $operation->transaction($this->session, [ + $transaction = $this->operation->transaction($this->session, [ 'transactionOptions' => ['excludeTxnFromChangeStreams' => true] ]); @@ -395,42 +425,37 @@ public function testExecuteAndExecuteUpdateWithExcludeTxnFromChangeStreams() $stream = $this->prophesize(ServerStream::class); $stream->readAll()->shouldBeCalledTimes(2)->willReturn([$resultSet]); - $gapic = $this->prophesize(SpannerClient::class); - $gapic->executeStreamingSql(self::SESSION, $sql, Argument::that(function (array $options) { - $this->assertArrayHasKey('transaction', $options); - $this->assertNotNull($transactionOptions = $options['transaction']->getBegin()); - $this->assertTrue($transactionOptions->getExcludeTxnFromChangeStreams()); - return true; - })) + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) { + $this->assertTrue($request->getTransaction()->getBegin()->getExcludeTxnFromChangeStreams()); + return true; + }), + Argument::type('array') + ) ->shouldBeCalledTimes(2) ->willReturn($stream->reveal()); - $operation = new Operation( - new Grpc(['gapicSpannerClient' => $gapic->reveal()]), - true - ); - - $operation->execute($this->session, $sql, [ + $this->operation->execute($this->session, $sql, [ 'transaction' => ['begin' => ['excludeTxnFromChangeStreams' => true]] ]); $transaction = $this->prophesize(Transaction::class)->reveal(); - $operation->executeUpdate($this->session, $transaction, $sql, [ + $this->operation->executeUpdate($this->session, $transaction, $sql, [ 'transaction' => ['begin' => ['excludeTxnFromChangeStreams' => true]] ]); } public function testSnapshot() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('session', $this->session->name()) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + return $request->getSession() == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $snap = $this->operation->snapshot($this->session); $this->assertInstanceOf(Snapshot::class, $snap); @@ -440,10 +465,7 @@ public function testSnapshot() public function testSnapshotSingleUse() { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotBeCalled(); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); $snap = $this->operation->snapshot($this->session, ['singleUse' => true]); $this->assertInstanceOf(Snapshot::class, $snap); @@ -453,14 +475,17 @@ public function testSnapshotSingleUse() public function testSnapshotWithTimestamp() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('database', self::DATABASE), - Argument::withEntry('session', $this->session->name()) - )) + $this->spannerClient->beginTransaction( + Argument::that(function ($request) { + return $request->getSession() == $this->session->name(); + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn(['id' => self::TRANSACTION, 'readTimestamp' => self::TIMESTAMP]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + ->willReturn(new TransactionProto([ + 'id' => self::TRANSACTION, + 'read_timestamp' => new TimestampProto(['seconds' => (new \DateTime(self::TIMESTAMP))->format('U')]) + ])); $snap = $this->operation->snapshot($this->session); $this->assertInstanceOf(Snapshot::class, $snap); @@ -477,28 +502,24 @@ public function testPartitionQuery() $partitionToken1 = 'token1'; $partitionToken2 = 'token2'; - $this->connection->partitionQuery(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('params', ['id' => '10']), - Argument::that(function ($arg) use ($transactionId) { - if ($arg['paramTypes']['id']['code'] !== Database::TYPE_INT64) { - return false; - } - - return $arg['transactionId'] === $transactionId; - }) - ))->shouldBeCalled()->willReturn([ - 'partitions' => [ - [ - 'partitionToken' => $partitionToken1 - ], [ - 'partitionToken' => $partitionToken2 + $this->spannerClient->partitionQuery( + Argument::that(function ($request) use ($sql, $transactionId, $partitionToken1, $partitionToken2) { + $this->assertEquals($request->getSql(), $sql); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertEquals(['id' => '10'], $request->getParams()->__debugInfo()); + $this->assertEquals(Database::TYPE_INT64, $request->getParamTypes()['id']->getCode()); + $this->assertEquals($transactionId, $request->getTransaction()->getId()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn(new PartitionResponse([ + 'partitions' => [ + new Partition(['partition_token' => $partitionToken1]), + new Partition(['partition_token' => $partitionToken2]), ] - ] - ]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + ])); $res = $this->operation->partitionQuery($this->session, $transactionId, $sql, [ 'parameters' => $params @@ -512,39 +533,36 @@ public function testPartitionQuery() public function testPartitionRead() { - $sql = 'SELECT * FROM Posts WHERE ID = @id'; $params = ['id' => 10]; $transactionId = 'foo'; $partitionToken1 = 'token1'; $partitionToken2 = 'token2'; - $this->connection->partitionRead(Argument::allOf( - Argument::withEntry('table', 'Posts'), - Argument::withEntry('session', self::SESSION), - Argument::withEntry('keySet', ['all' => true]), - Argument::withEntry('columns', ['foo']) - ))->shouldBeCalled()->willReturn([ - 'partitions' => [ - [ - 'partitionToken' => $partitionToken1 - ], [ - 'partitionToken' => $partitionToken2 + $this->spannerClient->partitionRead( + Argument::that(function ($request) { + $this->assertEquals('Posts', $request->getTable()); + $this->assertEquals(self::SESSION, $request->getSession()); + $this->assertEquals(true, $request->getKeySet()->getAll()); + $this->assertEquals(['foo'], $this->serializer->encodeMessage($request)['columns']); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn(new PartitionResponse([ + 'partitions' => [ + new Partition(['partition_token' => $partitionToken1]), + new Partition(['partition_token' => $partitionToken2]), ] - ] - ]); - - $this->operation->___setProperty('connection', $this->connection->reveal()); + ])); $res = $this->operation->partitionRead( $this->session, $transactionId, 'Posts', new KeySet(['all' => true]), - ['foo'], - [ - 'parameters' => $params - ] + ['foo'] ); $this->assertContainsOnlyInstancesOf(ReadPartition::class, $res); @@ -553,24 +571,44 @@ public function testPartitionRead() $this->assertEquals($partitionToken2, $res[1]->token()); } - private function executeAndReadResponse(array $additionalMetadata = []) + private function executeAndReadResponseStream(string $transactionId = null) + { + $stream = $this->prophesize(ServerStream::class); + $stream->readAll()->willReturn($this->executeAndReadResponse($transactionId)); + + return $stream->reveal(); + } + + private function executeAndReadResponse(string $transactionId = null) { - yield [ - 'metadata' => array_merge([ - 'rowType' => [ + $transactionMetadata = []; + if ($transactionId) { + $transactionMetadata = ['transaction' => new TransactionProto(['id' => $transactionId])]; + } + yield new PartialResultSet([ + 'metadata' => new ResultSetMetadata([ + 'row_type' => new StructType([ 'fields' => [ - [ + new Field([ 'name' => 'ID', - 'type' => [ - 'code' => Database::TYPE_INT64 - ] - ] + 'type' => new Type(['code' => Database::TYPE_INT64]) + ]), ] - ] - ], $additionalMetadata), + ]) + ] + $transactionMetadata), 'values' => [ - '10' + new Value(['string_value' => '10']) ] - ]; + ]); + } + + private function commitResponse($commit = []) + { + return new CommitResponse($commit + [ + 'commit_timestamp' => new TimestampProto([ + 'seconds' => (new \DateTime(self::TIMESTAMP))->format('U'), + 'nanos' => 534799000 + ]) + ]); } } diff --git a/Spanner/tests/Unit/PgJsonbTest.php b/Spanner/tests/Unit/PgJsonbTest.php index 59bc844aabd0..0450f6be6384 100644 --- a/Spanner/tests/Unit/PgJsonbTest.php +++ b/Spanner/tests/Unit/PgJsonbTest.php @@ -46,7 +46,7 @@ public function validValueProvider() { $obj = $this->prophesize('stdClass'); $obj->willImplement('JsonSerializable'); - $obj->jsonSerialize()->willReturn(["a" => 1, "b" => null]); + $obj->jsonSerialize()->willReturn(['a' => 1, 'b' => null]); return [ @@ -56,7 +56,7 @@ public function validValueProvider() // // null value shouldn't be casted [null, null], // // arrays should be converted to JSON - [["a"=>1.1, "b"=>"2"], '{"a":1.1,"b":"2"}'], + [['a' => 1.1, 'b' => '2'], '{"a":1.1,"b":"2"}'], // JsonSerializable should be used after a json_encode call [$obj->reveal(), '{"a":1,"b":null}'] ]; diff --git a/Spanner/tests/Unit/PgNumericTest.php b/Spanner/tests/Unit/PgNumericTest.php index 71cbd3d9542f..38671301fae5 100644 --- a/Spanner/tests/Unit/PgNumericTest.php +++ b/Spanner/tests/Unit/PgNumericTest.php @@ -18,8 +18,8 @@ namespace Google\Cloud\Spanner\Tests\Unit; use Google\Cloud\Spanner\PgNumeric; -use Google\Cloud\Spanner\V1\TypeCode; use Google\Cloud\Spanner\V1\TypeAnnotationCode; +use Google\Cloud\Spanner\V1\TypeCode; use PHPUnit\Framework\TestCase; /** diff --git a/Spanner/tests/Unit/ResultTest.php b/Spanner/tests/Unit/ResultTest.php index 05d8f17ac8e5..95232b932aa6 100644 --- a/Spanner/tests/Unit/ResultTest.php +++ b/Spanner/tests/Unit/ResultTest.php @@ -18,11 +18,14 @@ namespace Google\Cloud\Spanner\Tests\Unit; use Google\Cloud\Core\Exception\ServiceException; -use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\Operation; +use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Snapshot; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; +use Google\Cloud\Spanner\Transaction; use Google\Cloud\Spanner\ValueMapper; -use Google\Cloud\Core\Testing\GrpcTestTrait; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -35,9 +38,9 @@ class ResultTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; - use ResultTestTrait; + use ResultGeneratorTrait; - private $metadata = [ + private const METADATA = [ 'rowType' => [ 'fields' => [ [ @@ -47,10 +50,39 @@ class ResultTest extends TestCase ] ] ]; + private $operation; + private $session; + private $transaction; + private $snapshot; + private $mapper; public function setUp(): void { $this->checkAndSkipGrpcTests(); + + $this->operation = $this->prophesize(Operation::class); + $this->session = $this->prophesize(Session::class); + $this->transaction = $this->prophesize(Transaction::class); + $this->snapshot = $this->prophesize(Snapshot::class); + + $this->mapper = $this->prophesize(ValueMapper::class); + $this->mapper->decodeValues( + Argument::any(), + Argument::any(), + Argument::any() + )->will(function ($args) { + return $args[1]; + }); + + $this->operation->createSnapshot( + $this->session->reveal(), + Argument::type('array') + )->willReturn($this->snapshot->reveal()); + + $this->operation->createTransaction( + $this->session->reveal(), + Argument::type('array') + )->willReturn($this->transaction->reveal()); } /** @@ -58,31 +90,47 @@ public function setUp(): void */ public function testRows($chunks, $expectedValues) { - $result = iterator_to_array($this->getResultClass($chunks)->rows()); - $this->assertEquals($expectedValues, $result); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($chunks) { + return $this->resultGeneratorJson($chunks); + }, + 'r', + $this->mapper->reveal() + ); + $this->assertEquals($expectedValues, iterator_to_array($result->rows())); } public function testIterator() { - $fixture = $this->getStreamingDataFixture()['tests'][0]; - $result = iterator_to_array($this->getResultClass($fixture['chunks'])); + $fixtures = $this->getStreamingDataFixture()['tests'][0]; + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixtures) { + return $this->resultGeneratorJson($fixtures['chunks']); + }, + 'r', + $this->mapper->reveal() + ); - $this->assertEquals($fixture['result']['value'], $result); + $this->assertEquals($fixtures['result']['value'], iterator_to_array($result)); } public function testFailsWhenStreamThrowsUnrecoverableException() { $this->expectException(\Exception::class); - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () { - throw new \Exception; - } + throw new \Exception(); + }, + 'r', + $this->mapper->reveal() ); - iterator_to_array($result->rows()); } @@ -91,22 +139,19 @@ public function testResumesBrokenStream() $timesCalled = 0; $chunks = [ [ - 'metadata' => $this->metadata, + 'metadata' => self::METADATA, 'values' => ['a'] ], [ 'values' => ['b'], 'resumeToken' => 'abc' ], - [ - 'values' => ['c'] - ] + ['values' => ['c']] ]; - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () use ($chunks, &$timesCalled) { $timesCalled++; @@ -116,7 +161,9 @@ function () use ($chunks, &$timesCalled) { } yield $chunk; } - } + }, + 'r', + $this->mapper->reveal() ); iterator_to_array($result->rows()); @@ -128,21 +175,16 @@ public function testResumesAfterStreamStartFailure() $timesCalled = 0; $chunks = [ [ - 'metadata' => $this->metadata, + 'metadata' => self::METADATA, 'values' => ['a'] ], - [ - 'values' => ['b'] - ], - [ - 'values' => ['c'] - ] + ['values' => ['b']], + ['values' => ['c']] ]; - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () use ($chunks, &$timesCalled) { $timesCalled++; if ($timesCalled === 1) { @@ -152,7 +194,9 @@ function () use ($chunks, &$timesCalled) { foreach ($chunks as $key => $chunk) { yield $chunk; } - } + }, + 'r', + $this->mapper->reveal() ); iterator_to_array($result->rows()); @@ -164,21 +208,16 @@ public function testRowsRetriesWithoutResumeTokenWhenNotYieldedRows() $timesCalled = 0; $chunks = [ [ - 'metadata' => $this->metadata, + 'metadata' => self::METADATA, 'values' => ['a'] ], - [ - 'values' => ['b'] - ], - [ - 'values' => ['c'] - ] + ['values' => ['b']], + ['values' => ['c']] ]; - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () use ($chunks, &$timesCalled) { $timesCalled++; foreach ($chunks as $key => $chunk) { @@ -187,7 +226,9 @@ function () use ($chunks, &$timesCalled) { } yield $chunk; } - } + }, + 'r', + $this->mapper->reveal() ); iterator_to_array($result->rows()); @@ -199,22 +240,17 @@ public function testRowsRetriesWithResumeTokenWhenNotYieldedRows() $timesCalled = 0; $chunks = [ [ - 'metadata' => $this->metadata, + 'metadata' => self::METADATA, 'values' => ['a'], 'resumeToken' => 'abc' ], - [ - 'values' => ['b'] - ], - [ - 'values' => ['c'] - ] + ['values' => ['b']], + ['values' => ['c']] ]; - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () use ($chunks, &$timesCalled) { $timesCalled++; foreach ($chunks as $key => $chunk) { @@ -223,7 +259,9 @@ function () use ($chunks, &$timesCalled) { } yield $chunk; } - } + }, + 'r', + $this->mapper->reveal() ); iterator_to_array($result->rows()); @@ -236,18 +274,15 @@ public function testThrowsExceptionWhenCannotRetry() $chunks = [ [ - 'metadata' => $this->metadata, + 'metadata' => self::METADATA, 'values' => ['a'] ], - [ - 'values' => ['b'] - ] + ['values' => ['b']] ]; - $result = $this->getResultClass( - null, - 'r', - null, + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), function () use ($chunks) { foreach ($chunks as $key => $chunk) { if ($key === 1) { @@ -255,7 +290,9 @@ function () use ($chunks) { } yield $chunk; } - } + }, + 'r', + $this->mapper->reveal() ); iterator_to_array($result->rows()); @@ -264,7 +301,15 @@ function () use ($chunks) { public function testColumns() { $fixture = $this->getStreamingDataFixture()['tests'][0]; - $result = $this->getResultClass($fixture['chunks']); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $this->mapper->reveal() + ); $expectedColumnNames = ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7']; $this->assertNull($result->columns()); @@ -275,7 +320,15 @@ public function testColumns() public function testMetadata() { $fixture = $this->getStreamingDataFixture()['tests'][0]; - $result = $this->getResultClass($fixture['chunks']); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $this->mapper->reveal() + ); $expectedMetadata = json_decode($fixture['chunks'][0], true)['metadata']; $this->assertNull($result->stats()); @@ -286,7 +339,15 @@ public function testMetadata() public function testSession() { $fixture = $this->getStreamingDataFixture()['tests'][0]; - $result = $this->getResultClass($fixture['chunks']); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $this->mapper->reveal() + ); $this->assertInstanceOf(Session::class, $result->session()); } @@ -294,7 +355,15 @@ public function testSession() public function testStats() { $fixture = $this->getStreamingDataFixture()['tests'][1]; - $result = $this->getResultClass($fixture['chunks']); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $this->mapper->reveal() + ); $expectedStats = json_decode($fixture['chunks'][0], true)['stats']; $this->assertNull($result->stats()); @@ -305,7 +374,15 @@ public function testStats() public function testTransaction() { $fixture = $this->getStreamingDataFixture()['tests'][1]; - $result = $this->getResultClass($fixture['chunks'], 'rw'); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'rw', + $this->mapper->reveal() + ); $this->assertInstanceOf(Transaction::class, $result->transaction()); } @@ -313,7 +390,15 @@ public function testTransaction() public function testSnapshot() { $fixture = $this->getStreamingDataFixture()['tests'][1]; - $result = $this->getResultClass($fixture['chunks']); + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $this->mapper->reveal() + ); $this->assertInstanceOf(Snapshot::class, $result->snapshot()); } @@ -327,7 +412,16 @@ public function testUsesCorrectDefaultFormatOption() Argument::any(), 'associative' )->shouldBeCalled(); - $result = $this->getResultClass($fixture['chunks'], 'r', $mapper->reveal()); + + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $mapper->reveal() + ); $rows = $result->rows(); $rows->current(); @@ -345,7 +439,16 @@ public function testRecievesCorrectFormatOption($format) Argument::any(), $format )->shouldBeCalled(); - $result = $this->getResultClass($fixture['chunks'], 'r', $mapper->reveal()); + + $result = new Result( + $this->operation->reveal(), + $this->session->reveal(), + function () use ($fixture) { + return $this->resultGeneratorJson($fixture['chunks']); + }, + 'r', + $mapper->reveal() + ); $rows = $result->rows($format); $rows->current(); diff --git a/Spanner/tests/Unit/ResultTestTrait.php b/Spanner/tests/Unit/ResultTestTrait.php deleted file mode 100644 index 713fa4a14d01..000000000000 --- a/Spanner/tests/Unit/ResultTestTrait.php +++ /dev/null @@ -1,112 +0,0 @@ -<?php -/** - * Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit; - -use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Result; -use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\ValueMapper; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; - -trait ResultTestTrait -{ - use ProphecyTrait; - - public function streamingDataProvider() - { - foreach ($this->getStreamingDataFixture()['tests'] as $test) { - yield [$test['chunks'], $test['result']['value']]; - } - } - - public function streamingDataProviderFirstChunk() - { - foreach ($this->getStreamingDataFixture()['tests'] as $test) { - yield [$test['chunks'], $test['result']['value']]; - break; - } - } - - private function getResultClass( - $chunks = null, - $context = 'r', - $mapper = null, - $call = null - ) { - $operation = $this->prophesize(Operation::class); - $session = $this->prophesize(Session::class)->reveal(); - $transaction = $this->prophesize(Transaction::class); - $snapshot = $this->prophesize(Snapshot::class); - - if (!$mapper) { - $mapper = $this->prophesize(ValueMapper::class); - $mapper->decodeValues( - Argument::any(), - Argument::any(), - Argument::any() - )->will(function ($args) { - return $args[1]; - }); - $mapper = $mapper->reveal(); - } - - if (!$call) { - $call = function () use ($chunks) { - return $this->resultGenerator($chunks); - }; - } - - if ($context === 'r') { - $operation->createSnapshot( - $session, - Argument::type('array') - )->willReturn($snapshot->reveal()); - } else { - $operation->createTransaction( - $session, - Argument::type('array') - )->willReturn($transaction->reveal()); - } - - return new Result( - $operation->reveal(), - $session, - $call, - $context, - $mapper - ); - } - - private function resultGenerator($chunks) - { - foreach ($chunks as $chunk) { - yield json_decode($chunk, true); - } - } - - private function getStreamingDataFixture() - { - return json_decode( - file_get_contents(Fixtures::STREAMING_READ_ACCEPTANCE_FIXTURE()), - true - ); - } -} diff --git a/Spanner/tests/Unit/Session/CacheSessionPoolTest.php b/Spanner/tests/Unit/Session/CacheSessionPoolTest.php index 7a4fd47ee651..c80122b7ae5f 100644 --- a/Spanner/tests/Unit/Session/CacheSessionPoolTest.php +++ b/Spanner/tests/Unit/Session/CacheSessionPoolTest.php @@ -18,20 +18,23 @@ namespace Google\Cloud\Spanner\Tests\Unit\Session; use Google\Auth\Cache\MemoryCacheItemPool; +use Google\Cloud\Core\RequestHandler; +use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Lock\MockValues; -use Google\Cloud\Spanner\Connection\Grpc; use Google\Cloud\Spanner\Database; +use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\CacheSessionPool; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; +use Google\Protobuf\GPBEmpty; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\RejectedPromise; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Argument\ArgumentsWildcard; use Prophecy\PhpUnit\ProphecyTrait; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; use ReflectionMethod; /** @@ -42,6 +45,7 @@ class CacheSessionPoolTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; + use ResultGeneratorTrait; const CACHE_KEY_TEMPLATE = CacheSessionPool::CACHE_KEY_TEMPLATE; const PROJECT_ID = 'project'; @@ -84,7 +88,7 @@ public function badConfigDataProvider() [['maxCyclesToWaitForSession' => -1]], [['sleepIntervalSeconds' => -1]], [['minSessions' => 5, 'maxSessions' => 1]], - [['lock' => new \stdClass]] + [['lock' => new \stdClass()]] ]; } @@ -174,7 +178,7 @@ public function testAcquireThrowsExceptionWithNoAvailableSessions() public function testAcquireRemovesToCreateItemsIfCreateCallFails() { $exceptionThrown = false; - $config = ['maxSessions' => 1]; + $config = ['maxSessions' => 1, 'sleepIntervalSeconds' => 0]; $pool = new CacheSessionPoolStub($this->getCacheItemPool(), $config, $this->time); $pool->setDatabase($this->getDatabase(true)); @@ -195,9 +199,10 @@ public function testAcquireRemovesToCreateItemsIfCreateCallFails() public function testAcquireIfCreateSessionCallFails() { + $config = ['sleepIntervalSeconds' => 0]; $exceptionThrown = false; $exceptionMessage = null; - $pool = new CacheSessionPoolStub($this->getCacheItemPool()); + $pool = new CacheSessionPoolStub($this->getCacheItemPool(), $config); $pool->setDatabase($this->getDatabase(true)); try { @@ -855,9 +860,11 @@ public function acquireDataProvider() private function getDatabase($shouldCreateFails = false, $willDeleteSessions = false, $expectedCreateCalls = null) { - $database = $this->prophesize(DatabaseStub::class); - $session = $this->prophesize(SessionStub::class); - $connection = $this->prophesize(Grpc::class); + $database = $this->prophesize(Database::class); + $session = $this->prophesize(Session::class); + $requestHandler = $this->prophesize(RequestHandler::class); + $result = $this->prophesize(Result::class); + $result->rows()->willReturn($this->resultGeneratorJson([])); $session->expiration() ->willReturn($this->time + 3600); @@ -865,19 +872,14 @@ private function getDatabase($shouldCreateFails = false, $willDeleteSessions = f ->willReturn(false); if ($willDeleteSessions) { $session->delete() - ->willReturn(null); - $connection->deleteSessionAsync(Argument::any()) - ->willReturn(new FulfilledPromise( - new DumbObject() - )); + ->willReturn(null); + $database->deleteSessionAsync(Argument::any()) + ->willReturn(new FulfilledPromise(new GPBEmpty())); } else { - $connection->deleteSessionAsync(Argument::any()) - ->willReturn(new RejectedPromise( - new DumbObject() - )); + $database->deleteSessionAsync(Argument::any()) + ->willReturn(new RejectedPromise(new GPBEmpty())); } - $database->connection() - ->willReturn($connection->reveal()); + $database->session(Argument::any()) ->will(function ($args) use ($session) { $session->name() @@ -894,11 +896,11 @@ private function getDatabase($shouldCreateFails = false, $willDeleteSessions = f $database->name() ->willReturn(self::DATABASE_NAME); $database->execute(Argument::exact('SELECT 1'), Argument::withKey('session')) - ->willReturn(new DumbObject); + ->willReturn($result->reveal()); $createRes = function ($args, $mock, $method) use ($shouldCreateFails) { if ($shouldCreateFails) { - throw new \Exception("error"); + throw new \Exception('error'); } $methodCalls = $mock->findProphecyMethodCalls( @@ -916,11 +918,11 @@ private function getDatabase($shouldCreateFails = false, $willDeleteSessions = f }; if ($expectedCreateCalls) { - $connection->batchCreateSessions(Argument::any()) + $database->batchCreateSessions(Argument::any()) ->shouldBeCalledTimes($expectedCreateCalls) ->will($createRes); } else { - $connection->batchCreateSessions(Argument::any()) + $database->batchCreateSessions(Argument::any()) ->will($createRes); } @@ -1001,7 +1003,7 @@ public function testMaintainEmptyData() public function testMaintainException() { $data = $this->cacheData(['dead' => 3700, 'old' => 3200, 'fresh' => 100, 'other' => 1500], 300); - $database = $this->prophesize(DatabaseStub::class); + $database = $this->prophesize(Database::class); $database->identity()->willReturn([ 'projectId' => self::PROJECT_ID, 'database' => self::DATABASE_NAME, @@ -1049,7 +1051,7 @@ public function testMaintainServerDeletedSessions( $data = [] ) { $cacheData = $this->cacheData($initialItems, $maintainInterval); - $expiredTime = $this->time - 28*24*60*60; // 28 days + $expiredTime = $this->time - 28 * 24 * 60 * 60; // 28 days foreach ($cacheData['queue'] as $k => $v) { $cacheData['queue'][$k]['creation'] = $expiredTime; } @@ -1179,7 +1181,7 @@ public function testSessionPoolDatabaseRole() $config = ['minSessions' => 1, 'databaseRole' => 'Reader']; $cache = $this->getCacheItemPool($initialData); $pool = new CacheSessionPoolStub($cache, $config, $this->time); - $database = $this->prophesize(DatabaseStub::class); + $database = $this->prophesize(Database::class); $database->identity() ->willReturn([ 'projectId' => self::PROJECT_ID, @@ -1188,13 +1190,10 @@ public function testSessionPoolDatabaseRole() ]); $database->name() ->willReturn(self::DATABASE_NAME); - $connection = $this->prophesize(Grpc::class); - $connection->batchCreateSessions(['database' => self::DATABASE_NAME, + $database->batchCreateSessions([ 'sessionTemplate' => ['labels' => [], 'creator_role' => 'Reader'], 'sessionCount' => 1]) ->shouldBeCalled() - ->willReturn(['session' => array(['name' => 'session', 'expirtation' => $this->time])]); - $database->connection() - ->willReturn($connection->reveal()); + ->willReturn(['session' => [['name' => 'session', 'expirtation' => $this->time]]]); $pool->setDatabase($database->reveal()); $pool->warmup(); @@ -1217,40 +1216,4 @@ protected function time() return $this->time ?: parent::time(); } } - -class DatabaseStub extends Database -{ - // prevent "get_class() expects parameter 1 to be object" warning when debugging - public function __debugInfo() - { - return []; - } -} - -class SessionStub extends Session -{ - // prevent "get_class() expects parameter 1 to be object" warning when debugging - public function __debugInfo() - { - return []; - } -} - -class DumbObject -{ - public function __get($name) - { - return $this; - } - - public function __call($name, $args) - { - return $this; - } - - public function serializeToString() - { - return ''; - } -} //@codingStandardsIgnoreEnd diff --git a/Spanner/tests/Unit/SnapshotTest.php b/Spanner/tests/Unit/SnapshotTest.php index 04194e5b0c98..e88849a8e50a 100644 --- a/Spanner/tests/Unit/SnapshotTest.php +++ b/Spanner/tests/Unit/SnapshotTest.php @@ -17,17 +17,17 @@ namespace Google\Cloud\Spanner\Tests\Unit; +use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; +use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Snapshot; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Core\Testing\GrpcTestTrait; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Google\Cloud\Spanner\KeySet; -use Google\Cloud\Spanner\Result; /** * @group spanner @@ -45,7 +45,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->timestamp = new Timestamp(new \DateTime); + $this->timestamp = new Timestamp(new \DateTime()); $args = [ 'id' => 'foo', diff --git a/Spanner/tests/Unit/SpannerClientTest.php b/Spanner/tests/Unit/SpannerClientTest.php index beeb17033339..1abfdfafd0b8 100644 --- a/Spanner/tests/Unit/SpannerClientTest.php +++ b/Spanner/tests/Unit/SpannerClientTest.php @@ -17,30 +17,37 @@ namespace Google\Cloud\Spanner\Tests\Unit; +use Google\ApiCore\OperationResponse; +use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; +use Google\Cloud\Core\Testing\Snippet\Fixtures; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; +use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsResponse; +use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesResponse; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Bytes; use Google\Cloud\Spanner\CommitTimestamp; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\InstanceConfiguration; -use Google\Cloud\Spanner\PgJsonb; -use Google\Cloud\Spanner\PgOid; use Google\Cloud\Spanner\KeyRange; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Numeric; +use Google\Cloud\Spanner\PgJsonb; use Google\Cloud\Spanner\PgNumeric; +use Google\Cloud\Spanner\PgOid; use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; +use Google\Protobuf\Duration; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -53,22 +60,24 @@ class SpannerClientTest extends TestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const INSTANCE = 'inst'; const DATABASE = 'db'; const CONFIG = 'conf'; - private $client; - private $connection; + private $serializer; + private SpannerClient $spannerClient; + private $instanceAdminClient; private $directedReadOptionsIncludeReplicas; + private $operationResponse; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); + $this->directedReadOptionsIncludeReplicas = [ 'includeReplicas' => [ 'replicaSelections' => [ @@ -78,17 +87,23 @@ public function setUp(): void ] ] ]; - $this->client = TestHelpers::stub(SpannerClient::class, [ - [ - 'projectId' => self::PROJECT, - 'directedReadOptions' => $this->directedReadOptionsIncludeReplicas - ] + + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->spannerClient = new SpannerClient([ + 'projectId' => self::PROJECT, + 'credentials' => Fixtures::KEYFILE_STUB_FIXTURE(), + 'directedReadOptions' => $this->directedReadOptionsIncludeReplicas, + 'gapicSpannerInstanceAdminClient' => $this->instanceAdminClient->reveal() ]); + + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); } public function testBatch() { - $batch = $this->client->batch('foo', 'bar'); + $batch = $this->spannerClient->batch('foo', 'bar'); $this->assertInstanceOf(BatchClient::class, $batch); $ref = new \ReflectionObject($batch); @@ -96,8 +111,7 @@ public function testBatch() $prop->setAccessible(true); $this->assertEquals( - sprintf( - 'projects/%s/instances/%s/databases/%s', + GapicSpannerClient::databaseName( self::PROJECT, 'foo', 'bar' @@ -111,25 +125,34 @@ public function testBatch() */ public function testInstanceConfigurations() { - $this->connection->listInstanceConfigs( - Argument::withEntry('projectName', InstanceAdminClient::projectName(self::PROJECT)) - ) - ->shouldBeCalled() - ->willReturn([ - 'instanceConfigs' => [ - [ + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListInstanceConfigsResponse([ + 'instance_configs' => [ + new InstanceConfig([ 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG), - 'displayName' => 'Bar' - ], [ + 'display_name' => 'Bar' + ]), + new InstanceConfig([ 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG), - 'displayName' => 'Bat' - ] + 'display_name' => 'Bat' + ]), ] - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ])); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + + $this->instanceAdminClient->listInstanceConfigs( + Argument::that(function ($request) { + return $request->getParent() == InstanceAdminClient::projectName(self::PROJECT); + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn($pagedListResponse->reveal()); - $configs = $this->client->instanceConfigurations(); + $configs = $this->spannerClient->instanceConfigurations(); $this->assertInstanceOf(ItemIterator::class, $configs); @@ -144,34 +167,60 @@ public function testInstanceConfigurations() */ public function testPagedInstanceConfigurations() { - $firstCall = [ - 'instanceConfigs' => [ - [ - 'name' => 'projects/foo/instanceConfigs/bar', - 'displayName' => 'Bar' - ] - ], - 'nextPageToken' => 'fooBar' - ]; - - $secondCall = [ - 'instanceConfigs' => [ - [ - 'name' => 'projects/foo/instanceConfigs/bat', - 'displayName' => 'Bat' + $page1 = $this->prophesize(Page::class); + $page1->getResponseObject() + ->willReturn(new ListInstanceConfigsResponse([ + 'instance_configs' => [ + new InstanceConfig([ + 'name' => 'projects/foo/instanceConfigs/bar', + 'display_name' => 'Bar' + ]) + ], + 'next_page_token' => 'fooBar' + ])); + + $pagedListResponse1 = $this->prophesize(PagedListResponse::class); + $pagedListResponse1->getPage() + ->willReturn($page1->reveal()); + + $page2 = $this->prophesize(Page::class); + $page2->getResponseObject() + ->willReturn(new ListInstanceConfigsResponse([ + 'instance_configs' => [ + new InstanceConfig([ + 'name' => 'projects/foo/instanceConfigs/bat', + 'display_name' => 'Bat' + ]) ] - ] - ]; - - $this->connection->listInstanceConfigs( - Argument::withEntry('projectName', InstanceAdminClient::projectName(self::PROJECT)) + ])); + + $pagedListResponse2 = $this->prophesize(PagedListResponse::class); + $pagedListResponse2->getPage() + ->willReturn($page2->reveal()); + + $iteration = 0; + $this->instanceAdminClient->listInstanceConfigs( + Argument::that(function ($request) use (&$iteration) { + $iteration++; + return $this->serializer->encodeMessage($request)['parent'] + == InstanceAdminClient::projectName(self::PROJECT) && $iteration == 1; + }), + Argument::type('array') ) - ->shouldBeCalledTimes(2) - ->willReturn($firstCall, $secondCall); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ->shouldBeCalled() + ->willReturn($pagedListResponse1->reveal()); + + $this->instanceAdminClient->listInstanceConfigs( + Argument::that(function ($request) use (&$iteration) { + return $this->serializer->encodeMessage($request)['parent'] + == InstanceAdminClient::projectName(self::PROJECT) && $iteration == 2; + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn($pagedListResponse2->reveal()); - $configs = $this->client->instanceConfigurations(); + $configs = $this->spannerClient->instanceConfigurations(); $this->assertInstanceOf(ItemIterator::class, $configs); @@ -186,7 +235,7 @@ public function testPagedInstanceConfigurations() */ public function testInstanceConfiguration() { - $config = $this->client->instanceConfiguration('bar'); + $config = $this->spannerClient->instanceConfiguration('bar'); $this->assertInstanceOf(InstanceConfiguration::class, $config); $this->assertEquals('bar', InstanceAdminClient::parseName($config->name())['instance_config']); @@ -197,26 +246,30 @@ public function testInstanceConfiguration() */ public function testCreateInstance() { - $this->connection->createInstance(Argument::that(function ($arg) { - if ($arg['name'] !== InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)) { - return false; - } - - return $arg['config'] === InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG); - })) + $this->instanceAdminClient->createInstance( + Argument::that(function ($request) use (&$iteration) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['instance']['name'], + InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE) + ); + $this->assertEquals( + $message['instance']['config'], + InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG) + ); + return true; + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn([ - 'name' => 'operations/foo' - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ->willReturn($this->operationResponse->reveal()); $config = $this->prophesize(InstanceConfiguration::class); $config->name()->willReturn(InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG)); - $operation = $this->client->createInstance($config->reveal(), self::INSTANCE); + $operation = $this->spannerClient->createInstance($config->reveal(), self::INSTANCE); - $this->assertInstanceOf(LongRunningOperation::class, $operation); + $this->assertInstanceOf(OperationResponse::class, $operation); } /** @@ -224,32 +277,35 @@ public function testCreateInstance() */ public function testCreateInstanceWithNodes() { - $this->connection->createInstance(Argument::that(function ($arg) { - if ($arg['name'] !== InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)) { - return false; - } - - if ($arg['config'] !== InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG)) { - return false; - } - - return isset($arg['nodeCount']) && $arg['nodeCount'] === 2; - })) + $this->instanceAdminClient->createInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + if ($message['instance']['name'] !== InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)) { + return false; + } + + if ($message['instance']['config'] !== InstanceAdminClient::instanceConfigName( + self::PROJECT, + self::CONFIG + )) { + return false; + } + + return isset($message['instance']['nodeCount']) && $message['instance']['nodeCount'] === 2; + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn([ - 'name' => 'operations/foo' - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ->willReturn($this->operationResponse->reveal()); $config = $this->prophesize(InstanceConfiguration::class); $config->name()->willReturn(InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG)); - $operation = $this->client->createInstance($config->reveal(), self::INSTANCE, [ + $operation = $this->spannerClient->createInstance($config->reveal(), self::INSTANCE, [ 'nodeCount' => 2 ]); - $this->assertInstanceOf(LongRunningOperation::class, $operation); + $this->assertInstanceOf(OperationResponse::class, $operation); } /** @@ -257,32 +313,38 @@ public function testCreateInstanceWithNodes() */ public function testCreateInstanceWithProcessingUnits() { - $this->connection->createInstance(Argument::that(function ($arg) { - if ($arg['name'] !== InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)) { - return false; - } - - if ($arg['config'] !== InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG)) { - return false; - } - - return isset($arg['processingUnits']) && $arg['processingUnits'] === 2000; - })) + $this->instanceAdminClient->createInstance( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + if ($message['instance']['name'] !== InstanceAdminClient::instanceName( + self::PROJECT, + self::INSTANCE + )) { + return false; + } + if ($message['instance']['config'] !== InstanceAdminClient::instanceConfigName( + self::PROJECT, + self::CONFIG + )) { + return false; + } + + return isset($message['instance']['processingUnits']) + && $message['instance']['processingUnits'] === 2000; + }), + Argument::type('array') + ) ->shouldBeCalled() - ->willReturn([ - 'name' => 'operations/foo' - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ->willReturn($this->operationResponse->reveal()); $config = $this->prophesize(InstanceConfiguration::class); $config->name()->willReturn(InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG)); - $operation = $this->client->createInstance($config->reveal(), self::INSTANCE, [ + $operation = $this->spannerClient->createInstance($config->reveal(), self::INSTANCE, [ 'processingUnits' => 2000 ]); - $this->assertInstanceOf(LongRunningOperation::class, $operation); + $this->assertInstanceOf(OperationResponse::class, $operation); } /** @@ -294,7 +356,7 @@ public function testCreateInstanceRaisesInvalidArgument() $config = $this->prophesize(InstanceConfiguration::class); - $this->client->createInstance($config->reveal(), self::INSTANCE, [ + $this->spannerClient->createInstance($config->reveal(), self::INSTANCE, [ 'nodeCount' => 2, 'processingUnits' => 2000, ]); @@ -305,7 +367,7 @@ public function testCreateInstanceRaisesInvalidArgument() */ public function testInstance() { - $i = $this->client->instance('foo'); + $i = $this->spannerClient->instance('foo'); $this->assertInstanceOf(Instance::class, $i); $this->assertEquals('foo', InstanceAdminClient::parseName($i->name())['instance']); } @@ -315,7 +377,7 @@ public function testInstance() */ public function testInstanceWithInstanceArray() { - $i = $this->client->instance('foo', ['key' => 'val']); + $i = $this->spannerClient->instance('foo', ['key' => 'val']); $this->assertEquals('val', $i->info()['key']); } @@ -324,20 +386,31 @@ public function testInstanceWithInstanceArray() */ public function testInstances() { - $this->connection->listInstances( - Argument::withEntry('projectName', InstanceAdminClient::projectName(self::PROJECT)) - ) - ->shouldBeCalled() - ->willReturn([ + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListInstancesResponse([ 'instances' => [ - ['name' => 'projects/test-project/instances/foo'], - ['name' => 'projects/test-project/instances/bar'], + new InstanceProto(['name' => 'projects/test-project/instances/foo']), + new InstanceProto(['name' => 'projects/test-project/instances/bar']), ] - ]); - - $this->client->___setProperty('connection', $this->connection->reveal()); + ])); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + $this->instanceAdminClient->listInstances( + Argument::that(function ($request) { + $this->assertEquals( + $request->getParent(), + InstanceAdminClient::projectName(self::PROJECT) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalled() + ->willReturn($pagedListResponse->reveal()); - $instances = $this->client->instances(); + $instances = $this->spannerClient->instances(); $this->assertInstanceOf(ItemIterator::class, $instances); $instances = iterator_to_array($instances); @@ -353,108 +426,108 @@ public function testResumeOperation() { $opName = 'operations/foo'; - $op = $this->client->resumeOperation($opName); - $this->assertInstanceOf(LongRunningOperation::class, $op); - $this->assertEquals($op->name(), $opName); + $op = $this->spannerClient->resumeOperation($opName); + $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertEquals($op->getName(), $opName); } public function testConnect() { - $database = $this->client->connect(self::INSTANCE, self::DATABASE); + $database = $this->spannerClient->connect(self::INSTANCE, self::DATABASE); $this->assertInstanceOf(Database::class, $database); $this->assertEquals(self::DATABASE, DatabaseAdminClient::parseName($database->name())['database']); } public function testConnectWithInstance() { - $inst = $this->client->instance(self::INSTANCE); - $database = $this->client->connect($inst, self::DATABASE); + $inst = $this->spannerClient->instance(self::INSTANCE); + $database = $this->spannerClient->connect($inst, self::DATABASE); $this->assertInstanceOf(Database::class, $database); $this->assertEquals(self::DATABASE, DatabaseAdminClient::parseName($database->name())['database']); } public function testKeyset() { - $ks = $this->client->keySet(); + $ks = $this->spannerClient->keySet(); $this->assertInstanceOf(KeySet::class, $ks); } public function testKeyRange() { - $kr = $this->client->keyRange(); + $kr = $this->spannerClient->keyRange(); $this->assertInstanceOf(KeyRange::class, $kr); } public function testBytes() { - $b = $this->client->bytes('foo'); + $b = $this->spannerClient->bytes('foo'); $this->assertInstanceOf(Bytes::class, $b); - $this->assertEquals(base64_encode('foo'), (string)$b); + $this->assertEquals(base64_encode('foo'), (string) $b); } public function testDate() { - $d = $this->client->date(new \DateTime); + $d = $this->spannerClient->date(new \DateTime()); $this->assertInstanceOf(Date::class, $d); } public function testTimestamp() { - $ts = $this->client->timestamp(new \DateTime); + $ts = $this->spannerClient->timestamp(new \DateTime()); $this->assertInstanceOf(Timestamp::class, $ts); } public function testNumeric() { - $n = $this->client->numeric('12345.123456789'); + $n = $this->spannerClient->numeric('12345.123456789'); $this->assertInstanceOf(Numeric::class, $n); } public function testPgNumeric() { - $decimalVal = $this->client->pgNumeric('12345.123456789'); + $decimalVal = $this->spannerClient->pgNumeric('12345.123456789'); $this->assertInstanceOf(PgNumeric::class, $decimalVal); - $scientificVal = $this->client->pgNumeric('1.09E100'); + $scientificVal = $this->spannerClient->pgNumeric('1.09E100'); $this->assertInstanceOf(PgNumeric::class, $scientificVal); } public function testPgJsonB() { - $strVal = $this->client->pgJsonb('{}'); + $strVal = $this->spannerClient->pgJsonb('{}'); $this->assertInstanceOf(PgJsonb::class, $strVal); - $arrVal = $this->client->pgJsonb(["a" => 1, "b" => 2]); + $arrVal = $this->spannerClient->pgJsonb(['a' => 1, 'b' => 2]); $this->assertInstanceOf(PgJsonb::class, $arrVal); $stub = $this->prophesize('stdClass'); $stub->willImplement('JsonSerializable'); - $stub->jsonSerialize()->willReturn(["a" => 1, "b" => null]); - $objVal = $this->client->pgJsonb($stub->reveal()); + $stub->jsonSerialize()->willReturn(['a' => 1, 'b' => null]); + $objVal = $this->spannerClient->pgJsonb($stub->reveal()); $this->assertInstanceOf(PgJsonb::class, $objVal); } public function testPgOid() { - $oidVal = $this->client->pgOid('123'); + $oidVal = $this->spannerClient->pgOid('123'); $this->assertInstanceOf(PgOid::class, $oidVal); } public function testInt64() { - $i64 = $this->client->int64('123'); + $i64 = $this->spannerClient->int64('123'); $this->assertInstanceOf(Int64::class, $i64); } public function testDuration() { - $d = $this->client->duration(10, 1); + $d = $this->spannerClient->duration(10, 1); $this->assertInstanceOf(Duration::class, $d); } public function testCommitTimestamp() { - $t = $this->client->commitTimestamp(); + $t = $this->spannerClient->commitTimestamp(); $this->assertInstanceOf(CommitTimestamp::class, $t); } @@ -462,12 +535,12 @@ public function testSpannerClientDatabaseRole() { $instance = $this->prophesize(Instance::class); $instance->database(Argument::any(), ['databaseRole' => 'Reader'])->shouldBeCalled(); - $this->client->connect($instance->reveal(), self::DATABASE, ['databaseRole' => 'Reader']); + $this->spannerClient->connect($instance->reveal(), self::DATABASE, ['databaseRole' => 'Reader']); } public function testSpannerClientWithDirectedRead() { - $instance = $this->client->instance('testInstance'); + $instance = $this->spannerClient->instance('testInstance'); $this->assertEquals( $instance->directedReadOptions(), $this->directedReadOptionsIncludeReplicas diff --git a/Spanner/tests/Unit/StructTypeTest.php b/Spanner/tests/Unit/StructTypeTest.php index 465bbbda925e..d624e4db6679 100644 --- a/Spanner/tests/Unit/StructTypeTest.php +++ b/Spanner/tests/Unit/StructTypeTest.php @@ -54,7 +54,7 @@ public function testEnqueueInConstructor() public function testChainableAdd() { - $type = new StructType; + $type = new StructType(); $type->add($this->definition[0]['name'], $this->definition[0]['type']) ->add($this->definition[1]['name'], $this->definition[1]['child']); @@ -64,7 +64,7 @@ public function testChainableAdd() public function testAddUnnamed() { - $type = new StructType; + $type = new StructType(); $type->addUnnamed(Database::TYPE_STRING); $this->assertEquals($type->fields(), [ [ @@ -80,7 +80,7 @@ public function testAddInvalidType() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Field type `foo` is not valid.'); - (new StructType)->add('name', 'foo'); + (new StructType())->add('name', 'foo'); } /** @@ -90,7 +90,7 @@ public function testInvalidTypeDefinition($type) { $this->expectException(InvalidArgumentException::class); - (new StructType)->add('foo', $type); + (new StructType())->add('foo', $type); } public function definitionTypes() @@ -103,8 +103,8 @@ public function definitionTypes() public function testAddChildStruct() { - $str = new StructType; - $str->add('foo', new StructType); + $str = new StructType(); + $str->add('foo', new StructType()); $fields = $str->fields(); $this->assertEquals(Database::TYPE_STRUCT, $fields[0]['type']); @@ -113,7 +113,7 @@ public function testAddChildStruct() public function testAddChildArray() { - $str = new StructType; + $str = new StructType(); $str->add('foo', new ArrayType(null)); $fields = $str->fields(); diff --git a/Spanner/tests/Unit/StructValueTest.php b/Spanner/tests/Unit/StructValueTest.php index ab79505030ab..3fa728252f0a 100644 --- a/Spanner/tests/Unit/StructValueTest.php +++ b/Spanner/tests/Unit/StructValueTest.php @@ -50,7 +50,7 @@ public function testConstructor() public function testAdd() { - $val = new StructValue; + $val = new StructValue(); $val->add($this->values[0]['name'], $this->values[0]['value']) ->add($this->values[1]['name'], $this->values[1]['value']); @@ -59,7 +59,7 @@ public function testAdd() public function testAddUnnamed() { - $val = new StructValue; + $val = new StructValue(); $val->addUnnamed($this->values[0]['value']) ->addUnnamed($this->values[1]['value']); diff --git a/Spanner/tests/Unit/TimestampTest.php b/Spanner/tests/Unit/TimestampTest.php index 10fbbb0a764c..083d6ebbbf1a 100644 --- a/Spanner/tests/Unit/TimestampTest.php +++ b/Spanner/tests/Unit/TimestampTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Spanner\Timestamp; use PHPUnit\Framework\TestCase; /** @@ -56,7 +56,7 @@ public function testCast() { $this->assertEquals( (new \DateTime($this->dt->format(Timestamp::FORMAT)))->format('U'), - (new \DateTime(str_replace('000000000', '000000', (string)$this->ts)))->format('U') + (new \DateTime(str_replace('000000000', '000000', (string) $this->ts)))->format('U') ); } diff --git a/Spanner/tests/Unit/TransactionConfigurationTraitTest.php b/Spanner/tests/Unit/TransactionConfigurationTraitTest.php index 527d42706c98..8d8aaaa51e0a 100644 --- a/Spanner/tests/Unit/TransactionConfigurationTraitTest.php +++ b/Spanner/tests/Unit/TransactionConfigurationTraitTest.php @@ -19,10 +19,10 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\TimeTrait; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\TransactionConfigurationTrait; +use Google\Protobuf\Duration; use PHPUnit\Framework\TestCase; /** @@ -46,9 +46,9 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->impl = new TransactionConfigurationTraitImplementation; - $this->duration = new Duration(10, 1); - $this->dur = ['seconds' => 10, 'nanos' => 1]; + $this->impl = new TransactionConfigurationTraitImplementation(); + $this->duration = new Duration(['seconds' => 10, 'nanos' => 1]); + $this->dur = new Duration(['seconds' => 10, 'nanos' => 1]); $this->directedReadOptionsIncludeReplicas = [ 'includeReplicas' => [ 'replicaSelections' => [ @@ -157,14 +157,6 @@ public function testTransactionSelectorInvalidContext() $this->impl->proxyTransactionSelector($args); } - public function testConfigureSnapshotOptionsInvalidExactStaleness() - { - $this->expectException(\BadMethodCallException::class); - - $args = ['exactStaleness' => 'foo']; - $this->impl->proxyConfigureSnapshotOptions($args); - } - public function testConfigureSnapshotOptionsInvalidMaxStaleness() { $this->expectException(\BadMethodCallException::class); diff --git a/Spanner/tests/Unit/TransactionTest.php b/Spanner/tests/Unit/TransactionTest.php index 1563a9c9c9b6..fbbf361386dc 100644 --- a/Spanner/tests/Unit/TransactionTest.php +++ b/Spanner/tests/Unit/TransactionTest.php @@ -18,21 +18,29 @@ namespace Google\Cloud\Spanner\Tests\Unit; use Google\ApiCore\ValidationException; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Core\TimeTrait; use Google\Cloud\Spanner\BatchDmlResult; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\Tests\OperationRefreshTrait; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Tests\StubCreationTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\ReadRequest; +use Google\Cloud\Spanner\V1\ResultSet; +use Google\Cloud\Spanner\V1\ResultSetStats; +use Google\Cloud\Spanner\V1\RollbackRequest; +use Google\Protobuf\Duration; +use Google\Rpc\Status; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -44,11 +52,10 @@ class TransactionTest extends TestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; use ResultGeneratorTrait; - use StubCreationTrait; use TimeTrait; + use ApiHelperTrait; const TIMESTAMP = '2017-01-09T18:05:22.534799Z'; @@ -60,49 +67,44 @@ class TransactionTest extends TestCase const TRANSACTION_TAG = 'my-transaction-tag'; const REQUEST_TAG = 'my-request-tag'; - private $connection; private $instance; private $session; private $database; private $operation; + private $serializer; + private $headers; + private $spannerClient; private $transaction; - private $singleUseTransaction; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); - $this->operation = new Operation($this->connection->reveal(), false); + $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->operation = new Operation( + $this->spannerClient->reveal(), + $this->serializer, + false + ); $this->session = new Session( - $this->connection->reveal(), + $this->spannerClient->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION ); - $args = [ + $this->transaction = new Transaction( $this->operation, $this->session, self::TRANSACTION, false, self::TRANSACTION_TAG - ]; - - $props = [ - 'operation', 'readTimestamp', 'state' - ]; - - $this->transaction = TestHelpers::stub(Transaction::class, $args, $props); - - $args = [ - $this->operation, - $this->session, - ]; - $this->singleUseTransaction = TestHelpers::stub(Transaction::class, $args, $props); + ); } public function testSingleUseTagError() @@ -118,115 +120,25 @@ public function testSingleUseTagError() ); } - public function testInsert() - { - $this->transaction->insert('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['insert']['table']); - $this->assertEquals('foo', $mutations[0]['insert']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['insert']['values'][0]); - } - - public function testInsertBatch() - { - $this->transaction->insertBatch('Posts', [['foo' => 'bar']]); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['insert']['table']); - $this->assertEquals('foo', $mutations[0]['insert']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['insert']['values'][0]); - } - - public function testUpdate() - { - $this->transaction->update('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['update']['table']); - $this->assertEquals('foo', $mutations[0]['update']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['update']['values'][0]); - } - - public function testUpdateBatch() - { - $this->transaction->updateBatch('Posts', [['foo' => 'bar']]); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['update']['table']); - $this->assertEquals('foo', $mutations[0]['update']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['update']['values'][0]); - } - - public function testInsertOrUpdate() - { - $this->transaction->insertOrUpdate('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['insertOrUpdate']['table']); - $this->assertEquals('foo', $mutations[0]['insertOrUpdate']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['insertOrUpdate']['values'][0]); - } - - public function testInsertOrUpdateBatch() - { - $this->transaction->insertOrUpdateBatch('Posts', [['foo' => 'bar']]); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['insertOrUpdate']['table']); - $this->assertEquals('foo', $mutations[0]['insertOrUpdate']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['insertOrUpdate']['values'][0]); - } - - public function testReplace() - { - $this->transaction->replace('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['replace']['table']); - $this->assertEquals('foo', $mutations[0]['replace']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['replace']['values'][0]); - } - - public function testReplaceBatch() - { - $this->transaction->replaceBatch('Posts', [['foo' => 'bar']]); - - $mutations = $this->transaction->___getProperty('mutationData'); - - $this->assertEquals('Posts', $mutations[0]['replace']['table']); - $this->assertEquals('foo', $mutations[0]['replace']['columns'][0]); - $this->assertEquals('bar', $mutations[0]['replace']['values'][0]); - } - - public function testDelete() - { - $this->transaction->delete('Posts', new KeySet(['keys' => ['foo']])); - - $mutations = $this->transaction->___getProperty('mutationData'); - $this->assertEquals('Posts', $mutations[0]['delete']['table']); - $this->assertEquals('foo', $mutations[0]['delete']['keySet']['keys'][0]); - $this->assertArrayNotHasKey('all', $mutations[0]['delete']['keySet']); - } - public function testExecute() { $sql = 'SELECT * FROM Table'; - - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('sql', $sql), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $this->assertEquals($request->getTransaction()->getId(), self::TRANSACTION); + $this->assertEquals($request->getSql(), $sql); + return true; + }), + Argument::that(function (array $callOptions) { + $this->assertEquals( + $callOptions['headers']['x-goog-spanner-route-to-leader'], + ['true'] + ); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->transaction->execute($sql); $this->assertInstanceOf(Result::class, $res); @@ -237,19 +149,26 @@ public function testExecute() public function testExecuteUpdate() { $sql = 'UPDATE foo SET bar = @bar'; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('requestOptions', [ - 'requestTag' => self::REQUEST_TAG, - 'transactionTag' => self::TRANSACTION_TAG - ]), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true)); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); - $res = $this->transaction->executeUpdate($sql, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]]); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $this->assertEquals($request->getSql(), $sql); + $this->assertEquals($request->getTransaction()->getId(), self::TRANSACTION); + $this->assertEquals( + $request->getRequestOptions()->getRequestTag(), + self::REQUEST_TAG + ); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream(null, new ResultSetStats(['row_count_exact' => 1]))); + $res = $this->transaction->executeUpdate($sql, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]]); $this->assertEquals(1, $res); } @@ -269,109 +188,109 @@ public function testExecuteUpdateWithExcludeTxnFromChangeStreamsThrowsException( public function testDmlSeqno() { $sql = 'UPDATE foo SET bar = @bar'; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('seqno', 1), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true)); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); - $this->transaction->executeUpdate($sql, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]]); - - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('seqno', 2), - Argument::withEntry('requestOptions', [ - 'requestTag' => self::REQUEST_TAG, - 'transactionTag' => self::TRANSACTION_TAG - ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator(true)); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); - $this->transaction->executeUpdate($sql, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]]); - - $this->connection->executeBatchDml(Argument::allOf( - Argument::withEntry('seqno', 3), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]) - ))->shouldBeCalled()->willReturn([ - 'resultSets' => [] - ]); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) { + $this->assertEquals($request->getSeqno(), 1); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream(null, new ResultSetStats(['row_count_exact' => 1]))); + + $this->transaction->executeUpdate( + $sql, + ['requestOptions' => ['requestTag' => self::REQUEST_TAG]] + ); + + $this->spannerClient->executeBatchDml( + Argument::that(function (ExecuteBatchDmlRequest $request) { + $this->assertEquals( + $request->getSeqno(), + 2 + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteBatchDmlResponse(['result_sets' => []])); - $this->refreshOperation($this->transaction, $this->connection->reveal()); $this->transaction->executeUpdateBatch( - [ - ['sql' => 'SELECT 1'], - ], + [['sql' => 'SELECT 1']], ['requestOptions' => ['requestTag' => self::REQUEST_TAG]] ); } public function testExecuteUpdateBatch() { - $this->connection->executeBatchDml(Argument::allOf( - Argument::withEntry('statements', [ - [ - 'sql' => 'SELECT 1', - 'params' => [], - 'paramTypes' => [] - ], [ - 'sql' => 'SELECT @foo', - 'params' => [ - 'foo' => 'bar' - ], - 'paramTypes' => [ - 'foo' => [ - 'code' => Database::TYPE_STRING - ] - ] - ], [ - 'sql' => 'SELECT @foo', - 'params' => [ - 'foo' => null - ], - 'paramTypes' => [ - 'foo' => [ - 'code' => Database::TYPE_STRING - ] - ] - ] - ]), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn([ - 'resultSets' => [ - [ - 'stats' => [ - 'rowCountExact' => 1 - ] - ], [ - 'stats' => [ - 'rowCountExact' => 2 - ] - ], [ - 'stats' => [ - 'rowCountExact' => 3 - ] + $this->spannerClient->executeBatchDml( + Argument::that(function (ExecuteBatchDmlRequest $request) { + $this->assertEquals( + $request->getRequestOptions()->getRequestTag(), + self::REQUEST_TAG + ); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + $statements = $request->getStatements(); + $this->assertEquals(3, count($statements)); + + $statement1 = $statements[0]; + $this->assertEquals('SELECT 1', $statement1->getSql()); + $this->assertEmpty($statement1->getParams()); + $this->assertEmpty($statement1->getParamTypes()); + + $statement2 = $statements[1]; + $this->assertEquals('SELECT @foo', $statement2->getSql()); + $this->assertEquals('bar', $statement2->getParams()->getFields()['foo']->getStringValue()); + $types = $statement2->getParamTypes(); + $this->assertEquals(Database::TYPE_STRING, $types['foo']->getCode()); + + $statement3 = $statements[2]; + $this->assertEquals('SELECT @foo', $statement3->getSql()); + $this->assertEmpty($statement3->getParams()->getFields()['foo']->getStringValue()); + $types = $statement3->getParamTypes(); + $this->assertEquals(Database::TYPE_STRING, $types['foo']->getCode()); + return true; + }), + Argument::that(function (array $callOptions) { + $this->assertEquals( + $callOptions['headers']['x-goog-spanner-route-to-leader'], + ['true'] + ); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteBatchDmlResponse([ + 'result_sets' => [ + new ResultSet([ + 'stats' => new ResultSetStats([ + 'row_count_exact' => 1 + ]) + ]), + new ResultSet([ + 'stats' => new ResultSetStats([ + 'row_count_exact' => 2 + ]) + ]), + new ResultSet([ + 'stats' => new ResultSetStats([ + 'row_count_exact' => 3 + ]) + ]) ] - ] - ]); + ])); - $this->refreshOperation($this->transaction, $this->connection->reveal()); $res = $this->transaction->executeUpdateBatch( $this->bdmlStatements(), ['requestOptions' => ['requestTag' => self::REQUEST_TAG]] ); - $this->assertInstanceOf(BatchDmlResult::class, $res); $this->assertNull($res->error()); - $this->assertEquals([1,2,3], $res->rowCounts()); + $this->assertEquals([1, 2, 3], $res->rowCounts()); } public function testExecuteUpdateBatchError() @@ -382,35 +301,45 @@ public function testExecuteUpdateBatchError() 'details' => [] ]; - $this->connection->executeBatchDml(Argument::allOf( - Argument::withEntry('session', $this->session->name()), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]) - ))->shouldBeCalled()->willReturn([ - 'resultSets' => [ - [ - 'stats' => [ - 'rowCountExact' => 1 - ] - ], [ - 'stats' => [ - 'rowCountExact' => 2 - ] - ] - ], - 'status' => $err - ]); + $this->spannerClient->executeBatchDml( + Argument::that(function (ExecuteBatchDmlRequest $request) { + $this->assertEquals($request->getSession(), $this->session->name()); + $this->assertEquals( + $request->getRequestOptions()->getRequestTag(), + self::REQUEST_TAG + ); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteBatchDmlResponse([ + 'result_sets' => [ + new ResultSet([ + 'stats' => new ResultSetStats([ + 'row_count_exact' => 1 + ]) + ]), + new ResultSet([ + 'stats' => new ResultSetStats([ + 'row_count_exact' => 2 + ]) + ]) + ], + 'status' => new Status($err) + ])); - $this->refreshOperation($this->transaction, $this->connection->reveal()); $statements = $this->bdmlStatements(); $res = $this->transaction->executeUpdateBatch( $statements, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]] ); - $this->assertEquals([1,2], $res->rowCounts()); + $this->assertEquals([1, 2], $res->rowCounts()); $this->assertEquals($err, $res->error()['status']); $this->assertEquals($statements[2], $res->error()['statement']); } @@ -451,16 +380,25 @@ public function testExecuteUpdateNonDml() $this->expectException(InvalidArgumentException::class); $sql = 'UPDATE foo SET bar = @bar'; - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('sql', $sql), - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::that(function (ExecuteSqlRequest $request) use ($sql) { + $this->assertEquals($request->getSql(), $sql); + $this->assertEquals($request->getTransaction()->getId(), self::TRANSACTION); + $this->assertEquals( + $request->getRequestOptions()->getRequestTag(), + self::REQUEST_TAG + ); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); + $res = $this->transaction->executeUpdate($sql, ['requestOptions' => ['requestTag' => self::REQUEST_TAG]]); $this->assertEquals(1, $res); @@ -471,19 +409,34 @@ public function testRead() $table = 'Table'; $opts = ['foo' => 'bar']; - $this->connection->streamingRead(Argument::allOf( - Argument::withEntry('transaction', ['id' => self::TRANSACTION]), - Argument::withEntry('table', $table), - Argument::withEntry('keySet', ['all' => true]), - Argument::withEntry('columns', ['ID']), - Argument::withEntry('requestOptions', [ - 'transactionTag' => self::TRANSACTION_TAG, - 'requestTag' => self::REQUEST_TAG - ]), - Argument::withEntry('headers', ['x-goog-spanner-route-to-leader' => ['true']]) - ))->shouldBeCalled()->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::that(function (ReadRequest $request) use ($table) { + $this->assertEquals($request->getTransaction()->getId(), self::TRANSACTION); + $this->assertEquals($request->getTable(), $table); + $this->assertTrue($request->getKeySet()->getAll()); + $this->assertEquals(iterator_to_array($request->getColumns()), ['ID']); + $this->assertEquals( + $request->getRequestOptions()->getTransactionTag(), + self::TRANSACTION_TAG + ); + $this->assertEquals( + $request->getRequestOptions()->getRequestTag(), + self::REQUEST_TAG + ); + + return true; + }), + Argument::that(function (array $callOptions) { + $this->assertEquals( + $callOptions['headers']['x-goog-spanner-route-to-leader'], + ['true'] + ); + + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $res = $this->transaction->read( $table, @@ -499,37 +452,50 @@ public function testRead() public function testCommit() { - $this->transaction->insert('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - $operation = $this->prophesize(Operation::class); $operation->commitWithResponse( $this->session, - $mutations, + Argument::that(function ($mutations) { + $this->assertEquals(1, count($mutations)); + $this->assertEquals('Posts', $mutations[0]['insert']['table']); + $this->assertEquals('foo', $mutations[0]['insert']['columns'][0]); + $this->assertEquals('bar', $mutations[0]['insert']['values'][0]); + return true; + }), [ 'transactionId' => self::TRANSACTION, 'requestOptions' => [ 'transactionTag' => self::TRANSACTION_TAG ] ] - )->shouldBeCalled()->willReturn($this->commitResponseWithCommitStats()); + ) + ->shouldBeCalled() + ->willReturn($this->commitResponseWithCommitStats()); - $this->transaction->___setProperty('operation', $operation->reveal()); + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); - $this->transaction->commit(['requestOptions' => ['requestTag' => 'unused']]); + $transaction->insert('Posts', ['foo' => 'bar']); + $transaction->commit(['requestOptions' => ['requestTag' => 'unused']]); } public function testCommitWithReturnCommitStats() { - $this->transaction->insert('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); - $operation = $this->prophesize(Operation::class); $operation->commitWithResponse( $this->session, - $mutations, + Argument::that(function ($mutations) { + $this->assertEquals(1, count($mutations)); + $this->assertEquals('Posts', $mutations[0]['insert']['table']); + $this->assertEquals('foo', $mutations[0]['insert']['columns'][0]); + $this->assertEquals('bar', $mutations[0]['insert']['values'][0]); + return true; + }), [ 'transactionId' => self::TRANSACTION, 'returnCommitStats' => true, @@ -537,26 +503,39 @@ public function testCommitWithReturnCommitStats() 'transactionTag' => self::TRANSACTION_TAG ] ] - )->shouldBeCalled()->willReturn($this->commitResponseWithCommitStats()); + ) + ->shouldBeCalled() + ->willReturn($this->commitResponseWithCommitStats()); - $this->transaction->___setProperty('operation', $operation->reveal()); + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); - $this->transaction->commit(['returnCommitStats' => true]); + $transaction->insert('Posts', ['foo' => 'bar']); + $transaction->commit(['returnCommitStats' => true]); - $this->assertEquals(['mutationCount' => 1], $this->transaction->getCommitStats()); + $this->assertEquals(['mutationCount' => 1], $transaction->getCommitStats()); } public function testCommitWithMaxCommitDelay() { - $duration = new Duration(0, 100000000); - $this->transaction->insert('Posts', ['foo' => 'bar']); - - $mutations = $this->transaction->___getProperty('mutationData'); + $duration = new Duration(['seconds' => 0, 'nanos' => 100000000]); $operation = $this->prophesize(Operation::class); $operation->commitWithResponse( $this->session, - $mutations, + Argument::that(function ($mutations) { + $this->assertEquals(1, count($mutations)); + $this->assertEquals('Posts', $mutations[0]['insert']['table']); + $this->assertEquals('foo', $mutations[0]['insert']['columns'][0]); + $this->assertEquals('bar', $mutations[0]['insert']['values'][0]); + + return true; + }), [ 'transactionId' => self::TRANSACTION, 'returnCommitStats' => true, @@ -565,32 +544,60 @@ public function testCommitWithMaxCommitDelay() 'transactionTag' => self::TRANSACTION_TAG ] ] - )->shouldBeCalled()->willReturn($this->commitResponseWithCommitStats()); - - $this->transaction->___setProperty('operation', $operation->reveal()); + ) + ->shouldBeCalled() + ->willReturn($this->commitResponseWithCommitStats()); - $this->transaction->commit([ + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); + $transaction->insert('Posts', ['foo' => 'bar']); + $transaction->commit([ 'returnCommitStats' => true, 'maxCommitDelay' => $duration ]); - $this->assertEquals(['mutationCount' => 1], $this->transaction->getCommitStats()); + $this->assertEquals(['mutationCount' => 1], $transaction->getCommitStats()); } public function testCommitInvalidState() { $this->expectException(\BadMethodCallException::class); - $this->transaction->___setProperty('state', 'foo'); - $this->transaction->commit(); + $operation = $this->prophesize(Operation::class); + $operation->commitWithResponse(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn([[]]); + + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); + + // call "commit" to mock closing the state + $transaction->commit(); + + // transaction is considered closed after the first commit, so this should throw an exception + $transaction->commit(); } public function testRollback() { - $this->connection->rollback(Argument::withEntry('session', $this->session->name())) - ->shouldBeCalled(); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->rollback( + Argument::that(function (RollbackRequest $request) { + $this->assertEquals($request->getSession(), $this->session->name()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); $this->transaction->rollback(); } @@ -599,8 +606,24 @@ public function testRollbackInvalidState() { $this->expectException(\BadMethodCallException::class); - $this->transaction->___setProperty('state', 'foo'); - $this->transaction->rollback(); + $operation = $this->prophesize(Operation::class); + $operation->commitWithResponse(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn([[]]); + + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); + + // call "commit" to mock closing the state + $transaction->commit(); + + // transaction is considered closed after the first commit, so this should throw an exception + $transaction->rollback(); } public function testId() @@ -610,17 +633,36 @@ public function testId() public function testState() { - $this->assertEquals(Transaction::STATE_ACTIVE, $this->transaction->state()); + $operation = $this->prophesize(Operation::class); + $operation->commitWithResponse(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn([[]]); - $this->transaction->___setProperty('state', Transaction::STATE_COMMITTED); - $this->assertEquals(Transaction::STATE_COMMITTED, $this->transaction->state()); + $transaction = new Transaction( + $operation->reveal(), + $this->session, + self::TRANSACTION, + false, + self::TRANSACTION_TAG + ); + + $this->assertEquals(Transaction::STATE_ACTIVE, $transaction->state()); + + // call "commit" to mock closing the state + $transaction->commit(); + + $this->assertEquals(Transaction::STATE_COMMITTED, $transaction->state()); } public function testInvalidReadContext() { $this->expectException(\BadMethodCallException::class); - $this->singleUseTransaction->execute('foo'); + $singleUseTransaction = new Transaction( + $this->operation, + $this->session, + ); + $singleUseTransaction->execute('foo'); } public function testIsRetryFalse() @@ -630,14 +672,12 @@ public function testIsRetryFalse() public function testIsRetryTrue() { - $args = [ + $transaction = new Transaction( $this->operation, $this->session, self::TRANSACTION, true - ]; - - $transaction = TestHelpers::stub(Transaction::class, $args); + ); $this->assertTrue($transaction->isRetry()); } @@ -645,11 +685,6 @@ public function testIsRetryTrue() // ******* // Helpers - private function commitResponse() - { - return ['commitTimestamp' => self::TIMESTAMP]; - } - private function commitResponseWithCommitStats() { $time = $this->parseTimeString(self::TIMESTAMP); @@ -662,11 +697,4 @@ private function commitResponseWithCommitStats() ] ]; } - - private function assertTimestampIsCorrect($res) - { - $ts = new \DateTimeImmutable($this->commitResponse()['commitTimestamp']); - - $this->assertEquals($ts->format('Y-m-d\TH:i:s\Z'), $res->get()->format('Y-m-d\TH:i:s\Z')); - } } diff --git a/Spanner/tests/Unit/TransactionTypeTest.php b/Spanner/tests/Unit/TransactionTypeTest.php index d228794c863b..5b672f81017f 100644 --- a/Spanner/tests/Unit/TransactionTypeTest.php +++ b/Spanner/tests/Unit/TransactionTypeTest.php @@ -17,23 +17,39 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; +use Google\Cloud\Spanner\Serializer; +use Google\ApiCore\ServerStream; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Testing\GrpcTestTrait; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Core\TimeTrait; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceAdminClient; -use Google\Cloud\Spanner\Connection\ConnectionInterface; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\Duration; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Tests\StubCreationTrait; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\V1\SpannerClient; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\ReadRequest; +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; +use Google\Cloud\Spanner\V1\TransactionOptions\PBReadOnly; +use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite; +use Google\Protobuf\Duration; +use Google\Protobuf\Timestamp as TimestampProto; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -44,10 +60,10 @@ */ class TransactionTypeTest extends TestCase { + use ApiHelperTrait; use GrpcTestTrait; use ProphecyTrait; - use ResultTestTrait; - use StubCreationTrait; + use ResultGeneratorTrait; use TimeTrait; const PROJECT = 'my-project'; @@ -56,43 +72,80 @@ class TransactionTypeTest extends TestCase const TRANSACTION = 'my-transaction'; const SESSION = 'my-session'; - private $connection; - + private $spannerClient; + private $serializer; private $timestamp; + private $protoTimestamp; public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->timestamp = (new Timestamp(\DateTime::createFromFormat('U', time()), 500000005))->formatAsString(); + $time = \DateTime::createFromFormat('U', time()); + $nanos = 500000005; + $this->timestamp = (new Timestamp($time, $nanos))->formatAsString(); + $this->protoTimestamp = new TimestampProto(['seconds' => $time->format('U'), 'nanos' => $nanos]); - $this->connection = $this->getConnStub(); + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->serializer = $this->prophesize(Serializer::class); - $this->connection->createSession( - Argument::withEntry('database', SpannerClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE)) + // mock serializer responses for sessions (used for streaming tests) + $this->serializer = $this->prophesize(Serializer::class); + $this->serializer->decodeMessage( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + ) + ->willReturn(new CreateSessionRequest([ + 'database' => SpannerClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ])); + $this->serializer->encodeMessage(Argument::type(Session::class)) + ->willReturn(['name' => $this->getFullyQualifiedSessionName()]); + + $this->serializer->decodeMessage( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') ) - ->willReturn(['name' => SpannerClient::sessionName( - self::PROJECT, - self::INSTANCE, - self::DATABASE, - self::SESSION - )]); + ->willReturn(new DeleteSessionRequest()); + + $this->spannerClient->createSession( + Argument::that(function (CreateSessionRequest $request) { + $this->assertEquals( + $request->getDatabase(), + SpannerClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) + ); + return true; + }), + Argument::type('array') + ) + ->willReturn(new Session(['name' => $this->getFullyQualifiedSessionName()])); + + $this->spannerClient->deleteSession(Argument::cetera()) + ->shouldBeCalledOnce(); } public function testDatabaseRunTransactionPreAllocate() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readWrite' => [] - ]) - ))->shouldBeCalledTimes(1)->willReturn(['id' => self::TRANSACTION]); - - $this->connection->commit(Argument::withEntry('transactionId', self::TRANSACTION)) - ->shouldBeCalledTimes(1) - ->willReturn(['commitTimestamp' => $this->timestamp]); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals($request->getSession(), $this->getFullyQualifiedSessionName()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); + + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals($request->getTransactionId(), self::TRANSACTION); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse(['commit_timestamp' => $this->protoTimestamp])); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $database->runTransaction(function ($t) { // Transaction gets created at the commit operation @@ -102,14 +155,22 @@ public function testDatabaseRunTransactionPreAllocate() public function testDatabaseRunTransactionSingleUse() { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->commit(Argument::withEntry('singleUseTransaction', ['readWrite' => []])) - ->shouldBeCalledTimes(1) - ->willReturn(['commitTimestamp' => $this->timestamp]); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals( + $request->getSingleUseTransaction(), + $this->createTransactionOptions() + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse(['commit_timestamp' => $this->protoTimestamp])); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $database->runTransaction(function ($t) { $this->assertNull($t->id()); @@ -120,15 +181,17 @@ public function testDatabaseRunTransactionSingleUse() public function testDatabaseTransactionPreAllocate() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readWrite' => [] - ]) - ))->shouldBeCalledTimes(1)->willReturn(['id' => self::TRANSACTION]); - - $database = $this->database($this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals($request->getOptions(), $this->createTransactionOptions()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); + $database = $this->database($this->spannerClient->reveal()); $transaction = $database->transaction(); $this->assertInstanceOf(Transaction::class, $transaction); @@ -137,10 +200,9 @@ public function testDatabaseTransactionPreAllocate() public function testDatabaseTransactionSingleUse() { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $transaction = $database->transaction(['singleUse' => true]); @@ -150,17 +212,22 @@ public function testDatabaseTransactionSingleUse() public function testDatabaseSnapshotPreAllocate() { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readOnly' => [ - 'strong' => true - ] - ]) - ))->shouldBeCalledTimes(1) - ->willReturn(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals( + $request->getOptions(), + $this->createTransactionOptions(['readOnly' => ['strong' => true]]) + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $database = $this->database($this->connection->reveal()); + $database = $database = $this->database( + $this->spannerClient->reveal(), + ); $snapshot = $database->snapshot(); @@ -170,10 +237,11 @@ public function testDatabaseSnapshotPreAllocate() public function testDatabaseSnapshotSingleUse() { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $database = $this->database($this->connection->reveal()); + $database = $database = $this->database( + $this->spannerClient->reveal(), + ); $snapshot = $database->snapshot(['singleUse' => true]); @@ -191,25 +259,28 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch $time = $this->parseTimeString($this->timestamp); $timestamp = new Timestamp($time[0], $time[1]); - $duration = new Duration($seconds, $nanos); + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'minReadTimestamp' => $this->timestamp, - 'maxStaleness' => [ - 'seconds' => $seconds, - 'nanos' => $nanos - ] + 'minReadTimestamp' => ['seconds' => $time[0]->format('U'), 'nanos' => $time[1]], + 'maxStaleness' => $duration, ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; - $database = $this->database($this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'singleUse' => true, 'minReadTimestamp' => $timestamp, @@ -226,13 +297,11 @@ public function testDatabasePreAllocatedSnapshotMinReadTimestamp() $time = $this->parseTimeString($this->timestamp); $timestamp = new Timestamp($time[0], $time[1]); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $this->spannerClient->executeStreamingSql(Argument::cetera())->shouldNotBeCalled(); + $this->spannerClient->deleteSession(Argument::cetera())->shouldNotBeCalled(); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldNotbeCalled(); - - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $snapshot = $database->snapshot([ 'minReadTimestamp' => $timestamp, @@ -246,15 +315,13 @@ public function testDatabasePreAllocatedSnapshotMaxStaleness() $seconds = 1; $nanos = 2; - $duration = new Duration($seconds, $nanos); - - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $this->spannerClient->executeStreamingSql(Argument::cetera())->shouldNotBeCalled(); + $this->spannerClient->deleteSession(Argument::cetera())->shouldNotBeCalled(); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $snapshot = $database->snapshot([ 'maxStaleness' => $duration @@ -271,26 +338,27 @@ public function testDatabaseSnapshotSingleUseReadTimestampAndExactStaleness($chu $time = $this->parseTimeString($this->timestamp); $timestamp = new Timestamp($time[0], $time[1]); - $duration = new Duration($seconds, $nanos); - - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::allOf( - Argument::withEntry('transaction', [ - 'singleUse' => [ - 'readOnly' => [ - 'readTimestamp' => $this->timestamp, - 'exactStaleness' => [ - 'seconds' => $seconds, - 'nanos' => $nanos - ] - ] + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); + $transaction = [ + 'singleUse' => [ + 'readOnly' => [ + 'readTimestamp' => $this->formatTimestampForApi($this->timestamp), + 'exactStaleness' => $duration, ] - ]) - ))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ] + ]; + + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'singleUse' => true, @@ -311,28 +379,44 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c $time = $this->parseTimeString($this->timestamp); $timestamp = new Timestamp($time[0], $time[1]); - $duration = new Duration($seconds, $nanos); + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); + $options = [ + 'readOnly' => [ + 'readTimestamp' => $this->formatTimestampForApi($this->timestamp), + 'exactStaleness' => $duration, + ] + ]; + $transaction = new TransactionProto(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($transaction); - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readOnly' => [ - 'readTimestamp' => $this->timestamp, - 'exactStaleness' => [ - 'seconds' => $seconds, - 'nanos' => $nanos - ] - ] - ]) - ))->shouldBeCalledTimes(1)->willReturn([ - 'id' => self::TRANSACTION - ]); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); + + $this->serializer->decodeMessage( + Argument::type(BeginTransactionRequest::class), + Argument::that(function (array $data) use ($options) { + $this->assertEquals($data['options'], $options); + return true; + }), + ) + ->shouldBeCalledOnce() + ->willReturn(new BeginTransactionRequest()); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', ['id' => self::TRANSACTION])) - ->shouldBeCalledTimes(1) - ->willReturn($this->resultGenerator($chunks)); + $this->serializer->encodeMessage($transaction) + ->shouldBeCalledOnce() + ->willReturn([]); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, ['singleUse' => $options]); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'readTimestamp' => $timestamp, @@ -347,18 +431,25 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c */ public function testDatabaseSingleUseSnapshotStrongConsistency($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $transaction = [ 'singleUse' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'singleUse' => true, @@ -373,22 +464,42 @@ public function testDatabaseSingleUseSnapshotStrongConsistency($chunks) */ public function testDatabasePreAllocatedSnapshotStrongConsistency($chunks) { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readOnly' => [ - 'strong' => true - ] - ]) - ))->shouldBeCalledTimes(1)->willReturn([ - 'id' => self::TRANSACTION - ]); + $options = [ + 'readOnly' => [ + 'strong' => true + ] + ]; + $transaction = new TransactionProto(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($transaction); + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); + + $this->serializer->decodeMessage( + Argument::type(BeginTransactionRequest::class), + Argument::that(function (array $data) use ($options) { + $this->assertEquals($data['options'], $options); + return true; + }), + ) + ->shouldBeCalledOnce() + ->willReturn(new BeginTransactionRequest()); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', ['id' => self::TRANSACTION])) - ->shouldBeCalledTimes(1) - ->willReturn($this->resultGenerator($chunks)); + $this->serializer->encodeMessage($transaction) + ->shouldBeCalledOnce() + ->willReturn([]); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, ['singleUse' => $options]); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'strong' => true @@ -402,18 +513,23 @@ public function testDatabasePreAllocatedSnapshotStrongConsistency($chunks) */ public function testDatabaseSingleUseSnapshotDefaultsToStrongConsistency($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'singleUse' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'singleUse' => true, @@ -427,22 +543,42 @@ public function testDatabaseSingleUseSnapshotDefaultsToStrongConsistency($chunks */ public function testDatabasePreAllocatedSnapshotDefaultsToStrongConsistency($chunks) { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readOnly' => [ - 'strong' => true - ] - ]) - ))->shouldBeCalledTimes(1)->willReturn([ - 'id' => self::TRANSACTION - ]); + $options = [ + 'readOnly' => [ + 'strong' => true + ] + ]; + $transaction = new TransactionProto(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($transaction); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', ['id' => self::TRANSACTION])) - ->shouldBeCalledTimes(1) - ->willReturn($this->resultGenerator($chunks)); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); + + $this->serializer->decodeMessage( + Argument::type(BeginTransactionRequest::class), + Argument::that(function (array $data) use ($options) { + $this->assertEquals($data['options'], $options); + return true; + }), + ) + ->shouldBeCalledOnce() + ->willReturn(new BeginTransactionRequest()); - $database = $this->database($this->connection->reveal()); + $this->serializer->encodeMessage($transaction) + ->shouldBeCalledOnce() + ->willReturn([]); + + $serializer = $this->serializerForStreamingSql($chunks, ['singleUse' => $options]); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot(); @@ -454,22 +590,42 @@ public function testDatabasePreAllocatedSnapshotDefaultsToStrongConsistency($chu */ public function testDatabaseSnapshotReturnReadTimestamp($chunks) { - $this->connection->beginTransaction(Argument::allOf( - Argument::withEntry('singleUse', false), - Argument::withEntry('transactionOptions', [ - 'readOnly' => [ - 'returnReadTimestamp' => true - ] - ]) - ))->shouldBeCalledTimes(1)->willReturn([ - 'id' => self::TRANSACTION - ]); + $options = [ + 'readOnly' => [ + 'returnReadTimestamp' => true + ] + ]; + $transaction = new TransactionProto(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($transaction); + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); + + $this->serializer->decodeMessage( + Argument::type(BeginTransactionRequest::class), + Argument::that(function (array $data) use ($options) { + $this->assertEquals($data['options'], $options); + return true; + }), + ) + ->shouldBeCalledOnce() + ->willReturn(new BeginTransactionRequest()); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', ['id' => self::TRANSACTION])) - ->shouldBeCalledTimes(1) - ->willReturn($this->resultGenerator($chunks)); + $this->serializer->encodeMessage($transaction) + ->shouldBeCalledOnce() + ->willReturn([]); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, ['singleUse' => $options]); + $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'returnReadTimestamp' => true @@ -480,13 +636,20 @@ public function testDatabaseSnapshotReturnReadTimestamp($chunks) public function testDatabaseInsertSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals( + $request->getSingleUseTransaction(), + $this->createTransactionOptions() + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse(['commit_timestamp' => $this->protoTimestamp])); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $database->insert('Table', [ 'column' => 'value' @@ -495,13 +658,7 @@ public function testDatabaseInsertSingleUseReadWrite() public function testDatabaseInsertBatchSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->insertBatch('Table', [[ 'column' => 'value' @@ -510,13 +667,7 @@ public function testDatabaseInsertBatchSingleUseReadWrite() public function testDatabaseUpdateSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->update('Table', [ 'column' => 'value' @@ -525,13 +676,7 @@ public function testDatabaseUpdateSingleUseReadWrite() public function testDatabaseUpdateBatchSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->updateBatch('Table', [[ 'column' => 'value' @@ -540,13 +685,7 @@ public function testDatabaseUpdateBatchSingleUseReadWrite() public function testDatabaseInsertOrUpdateSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->insertOrUpdate('Table', [ 'column' => 'value' @@ -555,13 +694,7 @@ public function testDatabaseInsertOrUpdateSingleUseReadWrite() public function testDatabaseInsertOrUpdateBatchSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->insertOrUpdateBatch('Table', [[ 'column' => 'value' @@ -570,13 +703,7 @@ public function testDatabaseInsertOrUpdateBatchSingleUseReadWrite() public function testDatabaseReplaceSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->replace('Table', [ 'column' => 'value' @@ -585,13 +712,7 @@ public function testDatabaseReplaceSingleUseReadWrite() public function testDatabaseReplaceBatchSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); $database->replaceBatch('Table', [[ 'column' => 'value' @@ -600,15 +721,9 @@ public function testDatabaseReplaceBatchSingleUseReadWrite() public function testDatabaseDeleteSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); - - $database = $this->database($this->connection->reveal()); + $database = $this->createMockedCommitDatabase(); - $database->delete('Table', new KeySet); + $database->delete('Table', new KeySet()); } /** @@ -616,18 +731,23 @@ public function testDatabaseDeleteSingleUseReadWrite() */ public function testDatabaseExecuteSingleUseReadOnly($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'singleUse' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $database->execute('SELECT * FROM Table')->rows()->current(); } @@ -636,18 +756,25 @@ public function testDatabaseExecuteSingleUseReadOnly($chunks) */ public function testDatabaseExecuteBeginReadOnly($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $transaction = [ 'begin' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $database->execute('SELECT * FROM Table', [ 'begin' => true ])->rows()->current(); @@ -658,16 +785,21 @@ public function testDatabaseExecuteBeginReadOnly($chunks) */ public function testDatabaseExecuteBeginReadWrite($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'begin' => [ 'readWrite' => [] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); + $serializer = $this->serializerForStreamingSql($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); $database->execute('SELECT * FROM Table', [ 'begin' => true, 'transactionType' => SessionPoolInterface::CONTEXT_READWRITE @@ -679,19 +811,24 @@ public function testDatabaseExecuteBeginReadWrite($chunks) */ public function testDatabaseReadSingleUseReadOnly($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->streamingRead(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'singleUse' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); - $database->read('Table', new KeySet, [])->rows()->current(); + $serializer = $this->serializerForStreamingRead($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); + $database->read('Table', new KeySet(), [])->rows()->current(); } /** @@ -699,19 +836,25 @@ public function testDatabaseReadSingleUseReadOnly($chunks) */ public function testDatabaseReadBeginReadOnly($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->streamingRead(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'begin' => [ 'readOnly' => [ 'strong' => true ] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); - $database->read('Table', new KeySet, [], [ + $serializer = $this->serializerForStreamingRead($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); + $database->read('Table', new KeySet(), [], [ 'begin' => true ])->rows()->current(); } @@ -721,17 +864,22 @@ public function testDatabaseReadBeginReadOnly($chunks) */ public function testDatabaseReadBeginReadWrite($chunks) { - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->streamingRead(Argument::withEntry('transaction', [ + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $transaction = [ 'begin' => [ 'readWrite' => [] ] - ]))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ]; + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream($chunks)); - $database = $this->database($this->connection->reveal()); - $database->read('Table', new KeySet, [], [ + $serializer = $this->serializerForStreamingRead($chunks, $transaction); + $database = $this->database($this->spannerClient->reveal(), $serializer); + $database->read('Table', new KeySet(), [], [ 'begin' => true, 'transactionType' => SessionPoolInterface::CONTEXT_READWRITE ])->rows()->current(); @@ -739,23 +887,34 @@ public function testDatabaseReadBeginReadWrite($chunks) public function testTransactionPreAllocatedRollback() { - $this->connection->beginTransaction(Argument::withEntry('transactionOptions', [ - 'readWrite' => [] - ]))->shouldBeCalledTimes(1)->willReturn(['id' => self::TRANSACTION]); + $this->spannerClient->beginTransaction( + Argument::that(function (BeginTransactionRequest $request) { + $this->assertEquals($request->getOptions(), $this->createTransactionOptions()); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $sess = SpannerClient::sessionName( + $session = SpannerClient::sessionName( self::PROJECT, self::INSTANCE, self::DATABASE, self::SESSION ); + $this->spannerClient->rollback( + Argument::that(function ($request) use ($session) { + Argument::type(RollbackRequest::class); + $this->assertEquals($request->getTransactionId(), self::TRANSACTION); + $this->assertEquals($request->getSession(), $session); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->connection->rollback(Argument::allOf( - Argument::withEntry('transactionId', self::TRANSACTION), - Argument::withEntry('session', $sess) - ))->shouldBeCalled(); - - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $t = $database->transaction(); $t->rollback(); } @@ -764,32 +923,146 @@ public function testTransactionSingleUseRollback() { $this->expectException(\BadMethodCallException::class); - $this->connection->beginTransaction(Argument::any())->shouldNotbeCalled(); - $this->connection->rollback(Argument::any())->shouldNotbeCalled(); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $t = $database->transaction(['singleUse' => true]); $t->rollback(); } - private function database(ConnectionInterface $connection) + private function database(SpannerClient $spannerClient, Serializer $serializer = null) { - $operation = new Operation($connection, false); $instance = $this->prophesize(Instance::class); $instance->name()->willReturn(InstanceAdminClient::instanceName(self::PROJECT, self::INSTANCE)); $instance->directedReadOptions()->willReturn([]); - $database = TestHelpers::stub(Database::class, [ - $connection, + $database = new Database( + $spannerClient, + $this->prophesize(DatabaseAdminClient::class)->reveal(), + $serializer ?: new Serializer(), $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE - ], ['operation']); - - $database->___setProperty('operation', $operation); + ); return $database; } + + private function serializerForStreamingRead(array $chunks, array $expectedTransaction): Serializer + { + // mock serializer responses for streaming read + $this->serializer->decodeMessage( + Argument::type(ReadRequest::class), + Argument::that(function ($data) use ($expectedTransaction) { + $this->assertEquals($data['transaction'], $expectedTransaction); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn(new ReadRequest()); + + foreach ($chunks as $chunk) { + $result = new PartialResultSet(); + $result->mergeFromJsonString($chunk); + $this->serializer->encodeMessage($result) + ->shouldBeCalledOnce() + ->willReturn(json_decode($chunk, true)); + } + + return $this->serializer->reveal(); + } + + private function serializerForStreamingSql(array $chunks, array $expectedTransaction): Serializer + { + // mock serializer responses for streaming read + $this->serializer->decodeMessage( + Argument::type(ExecuteSqlRequest::class), + Argument::that(function ($data) use ($expectedTransaction) { + $this->assertEquals($data['transaction'], $expectedTransaction); + return true; + }) + ) + ->shouldBeCalledOnce() + ->willReturn(new ExecuteSqlRequest()); + + foreach ($chunks as $chunk) { + $result = new PartialResultSet(); + $result->mergeFromJsonString($chunk); + $this->serializer->encodeMessage($result) + ->shouldBeCalledOnce() + ->willReturn(json_decode($chunk, true)); + } + + return $this->serializer->reveal(); + } + + private function getFullyQualifiedSessionName() + { + return SpannerClient::sessionName( + self::PROJECT, + self::INSTANCE, + self::DATABASE, + self::SESSION + ); + } + + private function createTransactionOptions($options = []) + { + $serializer = new Serializer(); + $transactionOptions = new TransactionOptions(); + if (isset($options['readOnly'])) { + $readOnly = $serializer->decodeMessage( + new PBReadOnly(), + $options['readOnly'] + ); + $transactionOptions->setReadOnly($readOnly); + } else { + $readWrite = $readOnly = $serializer->decodeMessage( + new ReadWrite(), + $options['readOnly'] ?? [] + ); + $transactionOptions->setReadWrite($readWrite); + } + return $transactionOptions; + } + + private function createMockedCommitDatabase() + { + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) { + $this->assertEquals( + $request->getSingleUseTransaction(), + $this->createTransactionOptions() + ); + return true; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse(['commit_timestamp' => $this->protoTimestamp])); + + return $this->database($this->spannerClient->reveal()); + } + + // private function resultGeneratorStream(array $chunks) + // { + // foreach ($chunks as $i => $chunk) { + // $result = new PartialResultSet(); + // $result->mergeFromJsonString($chunk); + // $chunks[$i] = $result; + // } + // $this->stream = $this->prophesize(ServerStream::class); + // $this->stream->readAll() + // ->willReturn($this->resultGenerator($chunks)); + + // return $this->stream->reveal(); + // } + + // private function resultGenerator($chunks) + // { + // foreach ($chunks as $chunk) { + // yield $chunk; + // } + // } } diff --git a/Spanner/tests/Unit/V1/SpannerClientPartialVeneerTest.php b/Spanner/tests/Unit/V1/SpannerClientPartialVeneerTest.php deleted file mode 100644 index ff77aaddce50..000000000000 --- a/Spanner/tests/Unit/V1/SpannerClientPartialVeneerTest.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php -/** - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Google\Cloud\Spanner\Tests\Unit\V1; - -use Google\ApiCore\CredentialsWrapper; -use Google\ApiCore\Testing\MockTransport; -use Google\Cloud\Spanner\V1\SpannerClient; -use PHPUnit\Framework\TestCase; - -/** - * @group spanner - * @group gapic - */ -class SpannerClientPartialVeneerTest extends TestCase -{ - public function testGetTransport() - { - $options = [ - 'credentials' => $this->getMockBuilder(CredentialsWrapper::class) - ->disableOriginalConstructor() - ->getMock(), - ]; - - $client = new SpannerClient($options + [ - 'transport' => new MockTransport(null) - ]); - - $this->assertInstanceOf(MockTransport::class, $client->getTransport()); - } -} diff --git a/Spanner/tests/Unit/V1/SpannerClientTest.php b/Spanner/tests/Unit/V1/SpannerClientTest.php deleted file mode 100644 index 1de21c55dcc9..000000000000 --- a/Spanner/tests/Unit/V1/SpannerClientTest.php +++ /dev/null @@ -1,1161 +0,0 @@ -<?php -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * GENERATED CODE WARNING - * This file was automatically generated - do not edit! - */ - -namespace Google\Cloud\Spanner\Tests\Unit\V1; - -use Google\ApiCore\ApiException; -use Google\ApiCore\CredentialsWrapper; -use Google\ApiCore\ServerStream; -use Google\ApiCore\Testing\GeneratedTest; -use Google\ApiCore\Testing\MockTransport; -use Google\Cloud\Spanner\V1\BatchCreateSessionsResponse; -use Google\Cloud\Spanner\V1\BatchWriteResponse; -use Google\Cloud\Spanner\V1\CommitResponse; -use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; -use Google\Cloud\Spanner\V1\KeySet; -use Google\Cloud\Spanner\V1\ListSessionsResponse; -use Google\Cloud\Spanner\V1\PartialResultSet; -use Google\Cloud\Spanner\V1\PartitionResponse; -use Google\Cloud\Spanner\V1\ResultSet; -use Google\Cloud\Spanner\V1\Session; -use Google\Cloud\Spanner\V1\SpannerClient; -use Google\Cloud\Spanner\V1\Transaction; -use Google\Cloud\Spanner\V1\TransactionOptions; -use Google\Cloud\Spanner\V1\TransactionSelector; -use Google\Protobuf\GPBEmpty; -use Google\Rpc\Code; -use stdClass; - -/** - * @group spanner - * - * @group gapic - */ -class SpannerClientTest extends GeneratedTest -{ - /** @return TransportInterface */ - private function createTransport($deserialize = null) - { - return new MockTransport($deserialize); - } - - /** @return CredentialsWrapper */ - private function createCredentials() - { - return $this->getMockBuilder(CredentialsWrapper::class)->disableOriginalConstructor()->getMock(); - } - - /** @return SpannerClient */ - private function createClient(array $options = []) - { - $options += [ - 'credentials' => $this->createCredentials(), - ]; - return new SpannerClient($options); - } - - /** @test */ - public function batchCreateSessionsTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new BatchCreateSessionsResponse(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - $sessionCount = 185691686; - $response = $gapicClient->batchCreateSessions($formattedDatabase, $sessionCount); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/BatchCreateSessions', $actualFuncCall); - $actualValue = $actualRequestObject->getDatabase(); - $this->assertProtobufEquals($formattedDatabase, $actualValue); - $actualValue = $actualRequestObject->getSessionCount(); - $this->assertProtobufEquals($sessionCount, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function batchCreateSessionsExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - $sessionCount = 185691686; - try { - $gapicClient->batchCreateSessions($formattedDatabase, $sessionCount); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function batchWriteTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new BatchWriteResponse(); - $transport->addResponse($expectedResponse); - $expectedResponse2 = new BatchWriteResponse(); - $transport->addResponse($expectedResponse2); - $expectedResponse3 = new BatchWriteResponse(); - $transport->addResponse($expectedResponse3); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $mutationGroups = []; - $serverStream = $gapicClient->batchWrite($formattedSession, $mutationGroups); - $this->assertInstanceOf(ServerStream::class, $serverStream); - $responses = iterator_to_array($serverStream->readAll()); - $expectedResponses = []; - $expectedResponses[] = $expectedResponse; - $expectedResponses[] = $expectedResponse2; - $expectedResponses[] = $expectedResponse3; - $this->assertEquals($expectedResponses, $responses); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/BatchWrite', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getMutationGroups(); - $this->assertProtobufEquals($mutationGroups, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function batchWriteExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->setStreamingStatus($status); - $this->assertTrue($transport->isExhausted()); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $mutationGroups = []; - $serverStream = $gapicClient->batchWrite($formattedSession, $mutationGroups); - $results = $serverStream->readAll(); - try { - iterator_to_array($results); - // If the close stream method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function beginTransactionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $id = '27'; - $expectedResponse = new Transaction(); - $expectedResponse->setId($id); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $options = new TransactionOptions(); - $response = $gapicClient->beginTransaction($formattedSession, $options); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/BeginTransaction', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getOptions(); - $this->assertProtobufEquals($options, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function beginTransactionExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $options = new TransactionOptions(); - try { - $gapicClient->beginTransaction($formattedSession, $options); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function commitTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new CommitResponse(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $mutations = []; - $response = $gapicClient->commit($formattedSession, $mutations); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/Commit', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getMutations(); - $this->assertProtobufEquals($mutations, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function commitExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $mutations = []; - try { - $gapicClient->commit($formattedSession, $mutations); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function createSessionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $name = 'name3373707'; - $creatorRole = 'creatorRole-1605962583'; - $multiplexed = false; - $expectedResponse = new Session(); - $expectedResponse->setName($name); - $expectedResponse->setCreatorRole($creatorRole); - $expectedResponse->setMultiplexed($multiplexed); - $transport->addResponse($expectedResponse); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - $response = $gapicClient->createSession($formattedDatabase); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/CreateSession', $actualFuncCall); - $actualValue = $actualRequestObject->getDatabase(); - $this->assertProtobufEquals($formattedDatabase, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function createSessionExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - try { - $gapicClient->createSession($formattedDatabase); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function deleteSessionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new GPBEmpty(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedName = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $gapicClient->deleteSession($formattedName); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/DeleteSession', $actualFuncCall); - $actualValue = $actualRequestObject->getName(); - $this->assertProtobufEquals($formattedName, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function deleteSessionExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedName = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - try { - $gapicClient->deleteSession($formattedName); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeBatchDmlTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new ExecuteBatchDmlResponse(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $transaction = new TransactionSelector(); - $statements = []; - $seqno = 109325920; - $response = $gapicClient->executeBatchDml($formattedSession, $transaction, $statements, $seqno); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/ExecuteBatchDml', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getTransaction(); - $this->assertProtobufEquals($transaction, $actualValue); - $actualValue = $actualRequestObject->getStatements(); - $this->assertProtobufEquals($statements, $actualValue); - $actualValue = $actualRequestObject->getSeqno(); - $this->assertProtobufEquals($seqno, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeBatchDmlExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $transaction = new TransactionSelector(); - $statements = []; - $seqno = 109325920; - try { - $gapicClient->executeBatchDml($formattedSession, $transaction, $statements, $seqno); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeSqlTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new ResultSet(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - $response = $gapicClient->executeSql($formattedSession, $sql); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/ExecuteSql', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getSql(); - $this->assertProtobufEquals($sql, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeSqlExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - try { - $gapicClient->executeSql($formattedSession, $sql); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeStreamingSqlTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $chunkedValue = true; - $resumeToken2 = '90'; - $expectedResponse = new PartialResultSet(); - $expectedResponse->setChunkedValue($chunkedValue); - $expectedResponse->setResumeToken($resumeToken2); - $transport->addResponse($expectedResponse); - $chunkedValue2 = false; - $resumeToken3 = '91'; - $expectedResponse2 = new PartialResultSet(); - $expectedResponse2->setChunkedValue($chunkedValue2); - $expectedResponse2->setResumeToken($resumeToken3); - $transport->addResponse($expectedResponse2); - $chunkedValue3 = true; - $resumeToken4 = '92'; - $expectedResponse3 = new PartialResultSet(); - $expectedResponse3->setChunkedValue($chunkedValue3); - $expectedResponse3->setResumeToken($resumeToken4); - $transport->addResponse($expectedResponse3); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - $serverStream = $gapicClient->executeStreamingSql($formattedSession, $sql); - $this->assertInstanceOf(ServerStream::class, $serverStream); - $responses = iterator_to_array($serverStream->readAll()); - $expectedResponses = []; - $expectedResponses[] = $expectedResponse; - $expectedResponses[] = $expectedResponse2; - $expectedResponses[] = $expectedResponse3; - $this->assertEquals($expectedResponses, $responses); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/ExecuteStreamingSql', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getSql(); - $this->assertProtobufEquals($sql, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function executeStreamingSqlExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->setStreamingStatus($status); - $this->assertTrue($transport->isExhausted()); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - $serverStream = $gapicClient->executeStreamingSql($formattedSession, $sql); - $results = $serverStream->readAll(); - try { - iterator_to_array($results); - // If the close stream method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function getSessionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $name2 = 'name2-1052831874'; - $creatorRole = 'creatorRole-1605962583'; - $multiplexed = false; - $expectedResponse = new Session(); - $expectedResponse->setName($name2); - $expectedResponse->setCreatorRole($creatorRole); - $expectedResponse->setMultiplexed($multiplexed); - $transport->addResponse($expectedResponse); - // Mock request - $formattedName = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $response = $gapicClient->getSession($formattedName); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/GetSession', $actualFuncCall); - $actualValue = $actualRequestObject->getName(); - $this->assertProtobufEquals($formattedName, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function getSessionExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedName = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - try { - $gapicClient->getSession($formattedName); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function listSessionsTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $nextPageToken = ''; - $sessionsElement = new Session(); - $sessions = [ - $sessionsElement, - ]; - $expectedResponse = new ListSessionsResponse(); - $expectedResponse->setNextPageToken($nextPageToken); - $expectedResponse->setSessions($sessions); - $transport->addResponse($expectedResponse); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - $response = $gapicClient->listSessions($formattedDatabase); - $this->assertEquals($expectedResponse, $response->getPage()->getResponseObject()); - $resources = iterator_to_array($response->iterateAllElements()); - $this->assertSame(1, count($resources)); - $this->assertEquals($expectedResponse->getSessions()[0], $resources[0]); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/ListSessions', $actualFuncCall); - $actualValue = $actualRequestObject->getDatabase(); - $this->assertProtobufEquals($formattedDatabase, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function listSessionsExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedDatabase = $gapicClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - try { - $gapicClient->listSessions($formattedDatabase); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function partitionQueryTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new PartitionResponse(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - $response = $gapicClient->partitionQuery($formattedSession, $sql); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/PartitionQuery', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getSql(); - $this->assertProtobufEquals($sql, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function partitionQueryExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $sql = 'sql114126'; - try { - $gapicClient->partitionQuery($formattedSession, $sql); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function partitionReadTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new PartitionResponse(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $keySet = new KeySet(); - $response = $gapicClient->partitionRead($formattedSession, $table, $keySet); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/PartitionRead', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getTable(); - $this->assertProtobufEquals($table, $actualValue); - $actualValue = $actualRequestObject->getKeySet(); - $this->assertProtobufEquals($keySet, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function partitionReadExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $keySet = new KeySet(); - try { - $gapicClient->partitionRead($formattedSession, $table, $keySet); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function readTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new ResultSet(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $columns = []; - $keySet = new KeySet(); - $response = $gapicClient->read($formattedSession, $table, $columns, $keySet); - $this->assertEquals($expectedResponse, $response); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/Read', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getTable(); - $this->assertProtobufEquals($table, $actualValue); - $actualValue = $actualRequestObject->getColumns(); - $this->assertProtobufEquals($columns, $actualValue); - $actualValue = $actualRequestObject->getKeySet(); - $this->assertProtobufEquals($keySet, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function readExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $columns = []; - $keySet = new KeySet(); - try { - $gapicClient->read($formattedSession, $table, $columns, $keySet); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function rollbackTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $expectedResponse = new GPBEmpty(); - $transport->addResponse($expectedResponse); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $transactionId = '28'; - $gapicClient->rollback($formattedSession, $transactionId); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/Rollback', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getTransactionId(); - $this->assertProtobufEquals($transactionId, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function rollbackExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->addResponse(null, $status); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $transactionId = '28'; - try { - $gapicClient->rollback($formattedSession, $transactionId); - // If the $gapicClient method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function streamingReadTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $this->assertTrue($transport->isExhausted()); - // Mock response - $chunkedValue = true; - $resumeToken2 = '90'; - $expectedResponse = new PartialResultSet(); - $expectedResponse->setChunkedValue($chunkedValue); - $expectedResponse->setResumeToken($resumeToken2); - $transport->addResponse($expectedResponse); - $chunkedValue2 = false; - $resumeToken3 = '91'; - $expectedResponse2 = new PartialResultSet(); - $expectedResponse2->setChunkedValue($chunkedValue2); - $expectedResponse2->setResumeToken($resumeToken3); - $transport->addResponse($expectedResponse2); - $chunkedValue3 = true; - $resumeToken4 = '92'; - $expectedResponse3 = new PartialResultSet(); - $expectedResponse3->setChunkedValue($chunkedValue3); - $expectedResponse3->setResumeToken($resumeToken4); - $transport->addResponse($expectedResponse3); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $columns = []; - $keySet = new KeySet(); - $serverStream = $gapicClient->streamingRead($formattedSession, $table, $columns, $keySet); - $this->assertInstanceOf(ServerStream::class, $serverStream); - $responses = iterator_to_array($serverStream->readAll()); - $expectedResponses = []; - $expectedResponses[] = $expectedResponse; - $expectedResponses[] = $expectedResponse2; - $expectedResponses[] = $expectedResponse3; - $this->assertEquals($expectedResponses, $responses); - $actualRequests = $transport->popReceivedCalls(); - $this->assertSame(1, count($actualRequests)); - $actualFuncCall = $actualRequests[0]->getFuncCall(); - $actualRequestObject = $actualRequests[0]->getRequestObject(); - $this->assertSame('/google.spanner.v1.Spanner/StreamingRead', $actualFuncCall); - $actualValue = $actualRequestObject->getSession(); - $this->assertProtobufEquals($formattedSession, $actualValue); - $actualValue = $actualRequestObject->getTable(); - $this->assertProtobufEquals($table, $actualValue); - $actualValue = $actualRequestObject->getColumns(); - $this->assertProtobufEquals($columns, $actualValue); - $actualValue = $actualRequestObject->getKeySet(); - $this->assertProtobufEquals($keySet, $actualValue); - $this->assertTrue($transport->isExhausted()); - } - - /** @test */ - public function streamingReadExceptionTest() - { - $transport = $this->createTransport(); - $gapicClient = $this->createClient([ - 'transport' => $transport, - ]); - $status = new stdClass(); - $status->code = Code::DATA_LOSS; - $status->details = 'internal error'; - $expectedExceptionMessage = json_encode([ - 'message' => 'internal error', - 'code' => Code::DATA_LOSS, - 'status' => 'DATA_LOSS', - 'details' => [], - ], JSON_PRETTY_PRINT); - $transport->setStreamingStatus($status); - $this->assertTrue($transport->isExhausted()); - // Mock request - $formattedSession = $gapicClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - $table = 'table110115790'; - $columns = []; - $keySet = new KeySet(); - $serverStream = $gapicClient->streamingRead($formattedSession, $table, $columns, $keySet); - $results = $serverStream->readAll(); - try { - iterator_to_array($results); - // If the close stream method call did not throw, fail the test - $this->fail('Expected an ApiException, but no exception was thrown.'); - } catch (ApiException $ex) { - $this->assertEquals($status->code, $ex->getCode()); - $this->assertEquals($expectedExceptionMessage, $ex->getMessage()); - } - // Call popReceivedCalls to ensure the stub is exhausted - $transport->popReceivedCalls(); - $this->assertTrue($transport->isExhausted()); - } -} diff --git a/Spanner/tests/Unit/ValueMapperTest.php b/Spanner/tests/Unit/ValueMapperTest.php index 9b9780f3a2a0..5e36b057dad7 100644 --- a/Spanner/tests/Unit/ValueMapperTest.php +++ b/Spanner/tests/Unit/ValueMapperTest.php @@ -29,9 +29,9 @@ use Google\Cloud\Spanner\StructType; use Google\Cloud\Spanner\StructValue; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\ValueMapper; use Google\Cloud\Spanner\V1\TypeAnnotationCode; use Google\Cloud\Spanner\V1\TypeCode; +use Google\Cloud\Spanner\ValueMapper; use PHPUnit\Framework\TestCase; /** @@ -253,7 +253,7 @@ public function testFormatParamsForExecuteSqlArrayTypeNestedStruct() ]; $types = [ - 'foo' => new ArrayType((new StructType)->add('hello', Database::TYPE_STRING)) + 'foo' => new ArrayType((new StructType())->add('hello', Database::TYPE_STRING)) ]; $res = $this->mapper->formatParamsForExecuteSql($params, $types); @@ -314,7 +314,7 @@ public function testFormatParamsForExecuteSqlArrayMismatchedDefinition() $this->expectExceptionMessage('Array data does not match given array parameter type.'); $params = [ - 'foo' => [1,2,3] + 'foo' => [1, 2, 3] ]; $types = [ @@ -327,7 +327,7 @@ public function testFormatParamsForExecuteSqlArrayMismatchedDefinition() public function testFormatParamsForExecuteSqlArrayForCustomTypes() { $params = [ - 'foo' => [1,2,3] + 'foo' => [1, 2, 3] ]; $types = [ @@ -396,7 +396,7 @@ public function testFormatParamsForExecuteSqlStruct() ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add('name', Database::TYPE_STRING) ->add('age', Database::TYPE_INT64) ->add('jobs', new ArrayType(Database::TYPE_STRING)) @@ -484,7 +484,7 @@ public function testFormatParamsForExecuteSqlInvalidStructValue() ]; $types = [ - 'foo' => new StructType + 'foo' => new StructType() ]; $this->mapper->formatParamsForExecuteSql($params, $types); @@ -493,14 +493,14 @@ public function testFormatParamsForExecuteSqlInvalidStructValue() public function testFormatParamsForExecuteSqlStructDuplicateFieldNames() { $params = [ - 'foo' => (new StructValue) + 'foo' => (new StructValue()) ->add('hello', 'world') ->add('hello', 10) ->add('hello', 'goodbye') ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add('hello', Database::TYPE_STRING) ->add('hello', Database::TYPE_INT64) ->add('hello', Database::TYPE_STRING) @@ -543,7 +543,7 @@ public function testFormatParamsForExecuteSqlStructDuplicateFieldNames() public function testFormatParamsForExecuteSqlStructUnnamedFields() { $params = [ - 'foo' => (new StructValue) + 'foo' => (new StructValue()) ->addUnnamed('hello') ->addUnnamed(10) ->add('key', 'val') @@ -551,7 +551,7 @@ public function testFormatParamsForExecuteSqlStructUnnamedFields() ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add(null, Database::TYPE_STRING) ->addUnnamed(Database::TYPE_INT64) ->add('key', Database::TYPE_STRING) @@ -607,7 +607,7 @@ public function testFormatParamsForExecuteSqlInferredStructValueType() ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add('hello', Database::TYPE_STRING) ->add('num', Database::TYPE_INT64) ]; @@ -643,14 +643,14 @@ public function testFormatParamsForExecuteSqlInferredStructValueType() public function testFormatParamsForExecuteSqlInferredStructValueTypeWithUnnamed() { $params = [ - 'foo' => (new StructValue) + 'foo' => (new StructValue()) ->add('hello', 'world') ->addUnnamed('foo') ->add('num', 10) ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add('hello', Database::TYPE_STRING) ->add('num', Database::TYPE_INT64) ]; @@ -683,7 +683,7 @@ public function testFormatParamsForExecuteSqlStdClassValue() ]; $types = [ - 'foo' => (new StructType) + 'foo' => (new StructType()) ->add('hello', Database::TYPE_STRING) ]; @@ -921,7 +921,7 @@ public function testDecodeValuesString() public function testDecodeValuesTimestamp() { - $dt = new \DateTime; + $dt = new \DateTime(); $str = $dt->format(Timestamp::FORMAT); $res = $this->mapper->decodeValues( @@ -936,7 +936,7 @@ public function testDecodeValuesTimestamp() public function testDecodeValuesDate() { - $dt = new \DateTime; + $dt = new \DateTime(); $res = $this->mapper->decodeValues( $this->createField(Database::TYPE_DATE), $this->createRow($dt->format(Date::FORMAT)), diff --git a/Spanner/tests/Unit/bootstrap.php b/Spanner/tests/Unit/bootstrap.php new file mode 100644 index 000000000000..f16f16a2c9c5 --- /dev/null +++ b/Spanner/tests/Unit/bootstrap.php @@ -0,0 +1,12 @@ +<?php + +use DG\BypassFinals; + +// Make sure that while testing we bypass the `final` keyword for the GAPIC client. +BypassFinals::setWhitelist([ + '*/src/Admin/Database/V1/Client/*', + '*/src/Admin/Instance/V1/Client/*', + '*/src/V1/Client/*', +]); + +BypassFinals::enable(); diff --git a/Spanner/tests/Unit/fixtures/instance.json b/Spanner/tests/Unit/fixtures/instance.json deleted file mode 100644 index fcf371769ce3..000000000000 --- a/Spanner/tests/Unit/fixtures/instance.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "projects\/test-project\/instances\/instance-name", - "config": "projects\/test-project\/instanceConfigs\/regional-europe-west1", - "displayName": "Instance Name", - "nodeCount": 1, - "state": 2 -} diff --git a/composer.json b/composer.json index 9dd2b1fe0c72..c4c5a8f2297e 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,8 @@ "monolog/monolog": "^2.9||^3.0", "psr/http-message": "^1.0|^2.0", "ramsey/uuid": "^4.0", - "google/gax": "^1.34.0", + "google/gax": "dev-result-function as 1.40.0", + "google/common-protos": "^4.4", "google/auth": "^1.42" }, "require-dev": { @@ -64,7 +65,8 @@ "phpspec/prophecy-phpunit": "^2.1", "kreait/firebase-php": "^6.9", "psr/log": "^2.0||^3.0", - "dg/bypass-finals": "^1.7" + "dg/bypass-finals": "^1.7", + "dms/phpunit-arraysubset-asserts": "^0.5.0" }, "replace": { "google/access-context-manager": "1.0.1", diff --git a/dev/composer.json b/dev/composer.json index 45d6640be076..db66b13b5f3d 100644 --- a/dev/composer.json +++ b/dev/composer.json @@ -24,6 +24,7 @@ "phpspec/prophecy-phpunit": "^2.0", "swaggest/json-schema": "^0.12.0" }, + "minimum-stability": "dev", "repositories": { "google-cloud": { "type": "path", From 20bea687d480888f25f8a8ebfddd0cbd0c1063f6 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 18 Nov 2024 13:19:41 -0800 Subject: [PATCH 02/12] misc cleanup --- Core/src/Middleware/ExceptionMiddleware.php | 6 +- Core/src/RequestProcessorTrait.php | 5 +- Core/src/TimeTrait.php | 4 +- .../OperationResponseTraitTest.php | 2 - Spanner/src/ArrayType.php | 4 +- Spanner/src/Backup.php | 80 +++++------ Spanner/src/BatchDmlResult.php | 4 +- Spanner/src/Bytes.php | 6 +- Spanner/src/Database.php | 7 +- Spanner/src/Date.php | 22 +-- Spanner/src/Instance.php | 125 +++++++----------- Spanner/src/Operation.php | 80 ++++++----- Spanner/src/RequestTrait.php | 38 +++++- Spanner/src/SpannerClient.php | 68 ++++------ Spanner/test.php | 36 ----- Spanner/tests/Unit/TransactionTypeTest.php | 29 ++-- 16 files changed, 228 insertions(+), 288 deletions(-) delete mode 100644 Spanner/test.php diff --git a/Core/src/Middleware/ExceptionMiddleware.php b/Core/src/Middleware/ExceptionMiddleware.php index d99a253db2fb..0afffeb5bc3d 100644 --- a/Core/src/Middleware/ExceptionMiddleware.php +++ b/Core/src/Middleware/ExceptionMiddleware.php @@ -43,9 +43,9 @@ use Throwable; /** - * Middleware that adds autopopulation functionality. This middlware is - * added iff auto population settings are present in the resource - * descriptor config for the rpc method in context. + * Middleware that wraps any Api Exception to a `Google\Cloud\Core\Exception` + * exception class. This is primarily to maintain backwards compatibility with + * previous Spanner versions. * * @internal */ diff --git a/Core/src/RequestProcessorTrait.php b/Core/src/RequestProcessorTrait.php index 2f0dd1f41465..72440cd9194f 100644 --- a/Core/src/RequestProcessorTrait.php +++ b/Core/src/RequestProcessorTrait.php @@ -25,7 +25,6 @@ use \Google\Protobuf\Internal\Message; use Google\Rpc\RetryInfo; use Google\Rpc\BadRequest; -use GuzzleHttp\Promise\PromiseInterface; /** * @internal @@ -46,7 +45,7 @@ trait RequestProcessorTrait * Serializes a gRPC response. * * @param mixed $response - * @return \Generator|OperationResponse|array|PromiseInterface|null + * @return \Generator|OperationResponse|array|null */ private function handleResponse($response) { @@ -58,7 +57,7 @@ private function handleResponse($response) return $this->serializer->encodeMessage($response); } - if ($response instanceof OperationResponse || $response instanceof PromiseInterface) { + if ($response instanceof OperationResponse) { return $response; } diff --git a/Core/src/TimeTrait.php b/Core/src/TimeTrait.php index 773729b8bc1f..832f35d0e142 100644 --- a/Core/src/TimeTrait.php +++ b/Core/src/TimeTrait.php @@ -92,10 +92,10 @@ private function formatTimeAsString(\DateTimeInterface $dateTime, $ns) * $dateTime will be used instead. * @return array */ - private function formatTimeAsArray(\DateTimeInterface $dateTime, $ns) + private function formatTimeAsArray(\DateTimeInterface $dateTime, $ns = null) { if ($ns === null) { - $ns = $dateTime->format('u'); + $ns = $this->convertFractionToNanoSeconds($dateTime->format('u')); } return [ 'seconds' => (int) $dateTime->format('U'), diff --git a/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php b/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php index dcd57f9cbdc8..d47b162540d5 100644 --- a/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php +++ b/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php @@ -22,8 +22,6 @@ use Google\Cloud\Core\LongRunning\OperationResponseTrait; use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\LongRunning\LongRunningConnectionInterface; -use Google\Cloud\Core\RequestHandler; -use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; use Prophecy\Argument; use Google\Cloud\Audit\RequestMetadata; use Google\Cloud\Audit\AuthorizationInfo; diff --git a/Spanner/src/ArrayType.php b/Spanner/src/ArrayType.php index db3aec8bcf4a..7b8183b22feb 100644 --- a/Spanner/src/ArrayType.php +++ b/Spanner/src/ArrayType.php @@ -125,7 +125,7 @@ public function __construct($type) * @access private * @return int|string|null */ - public function type() + public function type(): int|string|null { return $this->type; } @@ -136,7 +136,7 @@ public function type() * @access private * @return StructType|null */ - public function structType() + public function structType(): StructType|null { return $this->structType; } diff --git a/Spanner/src/Backup.php b/Spanner/src/Backup.php index f050e4f42f08..5cbc27b8faed 100644 --- a/Spanner/src/Backup.php +++ b/Spanner/src/Backup.php @@ -101,22 +101,29 @@ public function __construct( * @return OperationResponse * @throws \InvalidArgumentException */ - public function create($database, DateTimeInterface $expireTime, array $options = []) - { + public function create( + $database, + DateTimeInterface $expireTime, + array $options = [] + ): OperationResponse { [$data, $callOptions] = $this->splitOptionalArgs($options); - $data = $this->validateAndFormatVersionTime($data); $data += [ 'parent' => $this->instance->name(), 'backupId' => DatabaseAdminClient::parseName($this->name)['backup'], 'backup' => [ 'database' => $this->instance->database($database)->name(), - 'expireTime' => $this->formatTimestampForApi($expireTime->format('Y-m-d\TH:i:s.u\Z')) + 'expireTime' => $this->formatTimeAsArray($expireTime), ], ]; - if (isset($data['versionTime'])) { - $data['backup']['versionTime'] = $data['versionTime']; - unset($data['versionTime']); + + if ($versionTime = $this->pluck('versionTime', $data, false)) { + if (!$versionTime instanceof DateTimeInterface) { + throw new \InvalidArgumentException( + 'Optional argument `versionTime` must be a DateTimeInterface' + ); + } + $data['backup']['versionTime'] = $this->formatTimeAsArray($versionTime); } $request = $this->serializer->decodeMessage(new CreateBackupRequest(), $data); @@ -150,14 +157,17 @@ public function create($database, DateTimeInterface $expireTime, array $options * @return OperationResponse * @throws \InvalidArgumentException */ - public function createCopy(Backup $newBackup, DateTimeInterface $expireTime, array $options = []) - { + public function createCopy( + Backup $newBackup, + DateTimeInterface $expireTime, + array $options = [] + ): OperationResponse { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ 'parent' => $newBackup->instance->name(), 'backupId' => DatabaseAdminClient::parseName($newBackup->name)['backup'], 'sourceBackup' => $this->fullyQualifiedBackupName($this->name), - 'expireTime' => $this->formatTimestampForApi($expireTime->format('Y-m-d\TH:i:s.u\Z')) + 'expireTime' => $this->formatTimeAsArray($expireTime) ]; $request = $this->serializer->decodeMessage(new CopyBackupRequest(), $data); @@ -178,7 +188,7 @@ public function createCopy(Backup $newBackup, DateTimeInterface $expireTime, arr * @param array $options [optional] Configuration options. * @return void */ - public function delete(array $options = []) + public function delete(array $options = []): void { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -206,7 +216,7 @@ public function delete(array $options = []) * @param array $options [optional] Configuration options. * @return bool */ - public function exists(array $options = []) + public function exists(array $options = []): bool { try { $this->reload($options); @@ -228,7 +238,7 @@ public function exists(array $options = []) * @param array $options [optional] Configuration options. * @return array */ - public function info(array $options = []) + public function info(array $options = []): array { if (!$this->info) { $this->info = $this->reload($options); @@ -246,7 +256,7 @@ public function info(array $options = []) * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -262,7 +272,7 @@ public function name() * @param array $options [optional] Configuration options. * @return array */ - public function reload(array $options = []) + public function reload(array $options = []): array { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -297,7 +307,7 @@ public function reload(array $options = []) * @param array $options [optional] Configuration options. * @return int|null */ - public function state(array $options = []) + public function state(array $options = []): int|null { $info = $this->info($options); @@ -317,17 +327,15 @@ public function state(array $options = []) * @param DateTimeInterface $newTimestamp New expire time. * @param array $options [optional] Configuration options. * - * @return Backup + * @return array */ - public function updateExpireTime(DateTimeInterface $newTimestamp, array $options = []) + public function updateExpireTime(DateTimeInterface $newTimestamp, array $options = []): array { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ 'backup' => [ 'name' => $this->name(), - 'expireTime' => $this->formatTimestampForApi( - $newTimestamp->format('Y-m-d\TH:i:s.u\Z') - ), + 'expireTime' => $this->formatTimeAsArray($newTimestamp), ], 'updateMask' => [ 'paths' => ['expire_time'] @@ -352,7 +360,7 @@ public function updateExpireTime(DateTimeInterface $newTimestamp, array $options * @param string $operationName The Long Running Operation name. * @return OperationResponse */ - public function resumeOperation($operationName, array $options = []) + public function resumeOperation($operationName, array $options = []): OperationResponse { return (new OperationResponse( $operationName, @@ -381,9 +389,9 @@ public function resumeOperation($operationName, array $options = []) * @type string $pageToken A previously-returned page token used to * resume the loading of results from a specific point. * } - * @return PagedListResponse<OperationResponse> + * @return ItemIterator<OperationResponse> */ - public function longRunningOperations(array $options = []) + public function longRunningOperations(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); @@ -401,7 +409,7 @@ public function longRunningOperations(array $options = []) * * @return string */ - private function fullyQualifiedBackupName($name) + private function fullyQualifiedBackupName($name): string { $instance = DatabaseAdminClient::parseName($this->instance->name())['instance']; @@ -418,28 +426,6 @@ private function fullyQualifiedBackupName($name) //@codeCoverageIgnoreEnd } - /** - * @param array $options - * @return array - */ - private function validateAndFormatVersionTime(array $options) - { - if (isset($options['versionTime'])) { - if (!($options['versionTime'] instanceof DateTimeInterface)) { - throw new \InvalidArgumentException( - 'Optional argument `versionTime` must be a DateTimeInterface, got ' . - (is_object($options['versionTime']) - ? get_class($options['versionTime']) - : gettype($options['versionTime'])) - ); - } - $options['versionTime'] = $this->formatTimestampForApi( - $options['versionTime']->format('Y-m-d\TH:i:s.u\Z') - ); - } - return $options; - } - private function backupResultFunction(): Closure { return function (BackupProto $backup) { diff --git a/Spanner/src/BatchDmlResult.php b/Spanner/src/BatchDmlResult.php index 25fa169f5f92..3b4350772c20 100644 --- a/Spanner/src/BatchDmlResult.php +++ b/Spanner/src/BatchDmlResult.php @@ -84,7 +84,7 @@ public function __construct(array $data, array $errorStatement = null) * * @return int[] */ - public function rowCounts() + public function rowCounts(): array { if (!$this->rowCounts) { foreach ($this->data['resultSets'] as $resultSet) { @@ -112,7 +112,7 @@ public function rowCounts() * * @return array|null */ - public function error() + public function error(): array|null { if ($this->errorStatement) { return [ diff --git a/Spanner/src/Bytes.php b/Spanner/src/Bytes.php index 7d7120031dcd..68657309aa57 100644 --- a/Spanner/src/Bytes.php +++ b/Spanner/src/Bytes.php @@ -63,7 +63,7 @@ public function __construct($value) * * @return StreamInterface */ - public function get() + public function get(): StreamInterface { return $this->value; } @@ -78,7 +78,7 @@ public function get() * * @return int */ - public function type() + public function type(): int { return Database::TYPE_BYTES; } @@ -93,7 +93,7 @@ public function type() * * @return string */ - public function formatAsString() + public function formatAsString(): string { return base64_encode((string) $this->value); } diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 6e69046e5bd2..d43a83ed3cf8 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -296,8 +296,11 @@ public function backups(array $options = []): ItemIterator * * @return OperationResponse<Backup> */ - public function createBackup($name, \DateTimeInterface $expireTime, array $options = []): OperationResponse - { + public function createBackup( + $name, + \DateTimeInterface $expireTime, + array $options = [] + ): OperationResponse { $backup = $this->instance->backup($name); return $backup->create($this->name(), $expireTime, $options); } diff --git a/Spanner/src/Date.php b/Spanner/src/Date.php index 547ee2b82486..8f172c84ec41 100644 --- a/Spanner/src/Date.php +++ b/Spanner/src/Date.php @@ -17,6 +17,8 @@ namespace Google\Cloud\Spanner; +use DateTimeInterface; + /** * Represents a value with a data type of * [Date](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.TypeCode). @@ -40,14 +42,14 @@ class Date implements ValueInterface const FORMAT = 'Y-m-d'; /** - * @var \DateTimeInterface + * @var DateTimeInterface */ protected $value; /** - * @param \DateTimeInterface $value The date value. + * @param DateTimeInterface $value The date value. */ - public function __construct(\DateTimeInterface $value) + public function __construct(DateTimeInterface $value) { $this->value = $value; } @@ -65,7 +67,7 @@ public function __construct(\DateTimeInterface $value) * @param int|string $day The day of the month. * @return Date */ - public static function createFromValues($year, $month, $day) + public static function createFromValues($year, $month, $day): Date { $value = sprintf('%s-%s-%s', $year, $month, $day); $dt = \DateTimeImmutable::createFromFormat(self::FORMAT, $value); @@ -74,16 +76,16 @@ public static function createFromValues($year, $month, $day) } /** - * Get the underlying `\DateTimeInterface` implementation. + * Get the underlying `DateTimeInterface` implementation. * * Example: * ``` * $dateTime = $date->get(); * ``` * - * @return \DateTimeInterface + * @return DateTimeInterface */ - public function get() + public function get(): DateTimeInterface { return $this->value; } @@ -98,7 +100,7 @@ public function get() * * @return int */ - public function type() + public function type(): int { return Database::TYPE_DATE; } @@ -113,7 +115,7 @@ public function type() * * @return string */ - public function formatAsString() + public function formatAsString(): string { return $this->value->format(self::FORMAT); } @@ -124,7 +126,7 @@ public function formatAsString() * @return string * @access private */ - public function __toString() + public function __toString(): string { return $this->formatAsString(); } diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index 1b2617915fba..276c29409e1a 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -141,7 +141,7 @@ public function __construct( * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -168,7 +168,7 @@ public function name() * * @return array */ - public function info(array $options = []) + public function info(array $options = []): array { if (!$this->info) { $this->reload($options); @@ -192,7 +192,7 @@ public function info(array $options = []) * @param array $options [optional] Configuration options. * @return bool */ - public function exists(array $options = []) + public function exists(array $options = []): bool { [$data, $callOptions] = $this->splitOptionalArgs($options); try { @@ -236,7 +236,7 @@ public function exists(array $options = []) * } * @return array */ - public function reload(array $options = []) + public function reload(array $options = []): array { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -287,7 +287,7 @@ public function reload(array $options = []) * @throws \InvalidArgumentException * @codingStandardsIgnoreEnd */ - public function create(InstanceConfiguration $config, array $options = []) + public function create(InstanceConfiguration $config, array $options = []): OperationResponse { list($instance, $callOptions) = $this->splitOptionalArgs($options); $instanceId = InstanceAdminClient::parseName($this->name)['instance']; @@ -332,7 +332,7 @@ public function create(InstanceConfiguration $config, array $options = []) * @param array $options [optional] Configuration options. * @return int|null */ - public function state(array $options = []) + public function state(array $options = []): int|null { $info = $this->info($options); @@ -371,7 +371,7 @@ public function state(array $options = []) * @return OperationResponse * @throws \InvalidArgumentException */ - public function update(array $options = []) + public function update(array $options = []): OperationResponse { list($instance, $callOptions) = $this->splitOptionalArgs($options); @@ -407,7 +407,7 @@ public function update(array $options = []) * @param array $options [optional] Configuration options. * @return void */ - public function delete(array $options = []) + public function delete(array $options = []): void { [$data, $callOptions] = $this->splitOptionalArgs($options); $data['name'] = $this->name; @@ -440,7 +440,7 @@ public function delete(array $options = []) * } * @return OperationResponse */ - public function createDatabase($name, array $options = []) + public function createDatabase($name, array $options = []): OperationResponse { $instantiation = $this->pluckArray(['sessionPool'], $options); @@ -464,7 +464,7 @@ public function createDatabase($name, array $options = []) * * @return OperationResponse */ - public function createDatabaseFromBackup($name, $backup, array $options = []) + public function createDatabaseFromBackup($name, $backup, array $options = []): OperationResponse { return $this->database($name)->createDatabaseFromBackup($name, $backup, $options); } @@ -493,7 +493,7 @@ public function createDatabaseFromBackup($name, $backup, array $options = []) * } * @return Database */ - public function database($name, array $options = []) + public function database($name, array $options = []): Database { return new Database( $this->spannerClient, @@ -537,34 +537,23 @@ public function database($name, array $options = []) * } * @return ItemIterator<Database> */ - public function databases(array $options = []) + public function databases(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); $data['parent'] = $this->name; - $resultLimit = $this->pluck('resultLimit', $data, false); - return new ItemIterator( - new PageIterator( - function (array $database) { - return $this->database($database['name'], ['database' => $database]); - }, - function ($callOptions) use ($data) { - if (isset($callOptions['pageToken'])) { - $data['pageToken'] = $callOptions['pageToken']; - } - - $request = $this->serializer->decodeMessage(new ListDatabasesRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - - $response = $this->databaseAdminClient->listDatabases($request, $callOptions); - return $this->handleResponse($response); - }, - $callOptions, - [ - 'itemsKey' => 'databases', - 'resultLimit' => $resultLimit - ] - ) + $request = $this->serializer->decodeMessage(new ListDatabasesRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->buildListItemsIterator( + [$this->databaseAdminClient, 'listDatabases'], + $request, + $callOptions, + function (array $database) { + return $this->database($database['name'], ['database' => $database]); + }, + 'databases', + $this->pluck('resultLimit', $options, false) ); } @@ -580,7 +569,7 @@ function ($callOptions) use ($data) { * * @return Backup */ - public function backup($name, array $backup = []) + public function backup($name, array $backup = []): Backup { return new Backup( $this->databaseAdminClient, @@ -623,37 +612,23 @@ public function backup($name, array $backup = []) * * @return ItemIterator<Backup> */ - public function backups(array $options = []) + public function backups(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); $data['parent'] = $this->name; - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $backup) { - return $this->backup( - $backup['name'], - $backup - ); - }, - function ($callOptions) use ($data) { - if (isset($callOptions['pageToken'])) { - $data['pageToken'] = $callOptions['pageToken']; - } - - $request = $this->serializer->decodeMessage(new ListBackupsRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - - $response = $this->databaseAdminClient->listBackups($request, $callOptions); - return $this->handleResponse($response); - }, - $callOptions, - [ - 'itemsKey' => 'backups', - 'resultLimit' => $resultLimit - ] - ) + $request = $this->serializer->decodeMessage(new ListBackupsRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->buildListItemsIterator( + [$this->databaseAdminClient, 'listBackups'], + $request, + $callOptions, + function (array $backup) { + return $this->backup($backup['name'], $backup); + }, + 'backups', + $this->pluck('resultLimit', $options, false) ); } @@ -683,7 +658,7 @@ function ($callOptions) use ($data) { * * @return ItemIterator<OperationResponse> */ - public function backupOperations(array $options = []) + public function backupOperations(array $options = []): ItemIterator { return $this->database($this->name)->backupOperations($options); } @@ -714,7 +689,7 @@ public function backupOperations(array $options = []) * * @return ItemIterator<OperationResponse> */ - public function databaseOperations(array $options = []) + public function databaseOperations(array $options = []): ItemIterator { return $this->database($this->name)->databaseOperations($options); } @@ -729,7 +704,7 @@ public function databaseOperations(array $options = []) * * @return IamManager */ - public function iam() + public function iam(): IamManager { if (!$this->iam) { $this->iam = new IamManager( @@ -750,7 +725,7 @@ public function iam() * @param string $project The project ID. * @return string */ - private function fullyQualifiedInstanceName($name, $project) + private function fullyQualifiedInstanceName($name, $project): string { return InstanceAdminClient::instanceName( $project, @@ -786,7 +761,7 @@ public function __debugInfo() * * @return array */ - public function directedReadOptions() + public function directedReadOptions(): array { return $this->directedReadOptions; } @@ -795,7 +770,7 @@ public function directedReadOptions() * @param array $instanceArray * @return array */ - private function fieldMask(array $instanceArray) + private function fieldMask(array $instanceArray): array { $mask = []; foreach (array_keys($instanceArray) as $key) { @@ -809,8 +784,10 @@ private function fieldMask(array $instanceArray) * @param InstanceConfiguration $config * @return array */ - public function createInstanceArray(array $instanceArray, InstanceConfiguration $config = null) - { + public function createInstanceArray( + array $instanceArray, + InstanceConfiguration $config = null + ): array { return $instanceArray + [ 'name' => $this->name, 'displayName' => InstanceAdminClient::parseName($this->name)['instance'], @@ -830,7 +807,7 @@ public function createInstanceArray(array $instanceArray, InstanceConfiguration * @param string $operationName The Long Running Operation name. * @return OperationResponse */ - public function resumeOperation($operationName, array $options = []) + public function resumeOperation($operationName, array $options = []): OperationResponse { return (new OperationResponse( $operationName, @@ -859,9 +836,9 @@ public function resumeOperation($operationName, array $options = []) * @type string $pageToken A previously-returned page token used to * resume the loading of results from a specific point. * } - * @return PagedListResponse<OperationResponse> + * @return ItemIterator<OperationResponse> */ - public function longRunningOperations(array $options = []) + public function longRunningOperations(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); diff --git a/Spanner/src/Operation.php b/Spanner/src/Operation.php index 96a215ab4fe8..488059eec401 100644 --- a/Spanner/src/Operation.php +++ b/Spanner/src/Operation.php @@ -120,7 +120,7 @@ public function __construct( * } * @return Timestamp The commit Timestamp. */ - public function commit(Session $session, array $mutations, array $options = []) + public function commit(Session $session, array $mutations, array $options = []): Timestamp { return $this->commitWithResponse($session, $mutations, $options)[0]; } @@ -151,7 +151,7 @@ public function commit(Session $session, array $mutations, array $options = []) * @return array An array containing {@see \Google\Cloud\Spanner\Timestamp} * at index 0 and the commit response as an array at index 1. */ - public function commitWithResponse(Session $session, array $mutations, array $options = []) + public function commitWithResponse(Session $session, array $mutations, array $options = []): array { [$data, $callOptions] = $this->splitOptionalArgs($options); $mutations = $this->serializeMutations($mutations); @@ -189,7 +189,7 @@ public function commitWithResponse(Session $session, array $mutations, array $op * @return void * @throws InvalidArgumentException If the transaction is not yet initialized. */ - public function rollback(Session $session, $transactionId, array $options = []) + public function rollback(Session $session, $transactionId, array $options = []): void { if (empty($transactionId)) { throw new InvalidArgumentException('Rollback failed: Transaction not initiated.'); @@ -231,7 +231,7 @@ public function rollback(Session $session, $transactionId, array $options = []) * } * @return Result */ - public function execute(Session $session, $sql, array $options = []) + public function execute(Session $session, $sql, array $options = []): Result { $options += [ 'parameters' => [], @@ -296,7 +296,7 @@ public function executeUpdate( Transaction $transaction, $sql, array $options = [] - ) { + ): int { if (!isset($options['transaction']['begin'])) { $options['transaction'] = ['id' => $transaction->id()]; } @@ -368,7 +368,7 @@ public function executeUpdateBatch( Transaction $transaction, array $statements, array $options = [] - ) { + ): BatchDmlResult { [$data, $callOptions] = $this->splitOptionalArgs($options); $data['transaction'] = $this->createTransactionSelector($data, $transaction->id()); $data += [ @@ -433,7 +433,7 @@ public function read( KeySet $keySet, array $columns, array $options = [] - ) { + ): Result { $context = $this->pluck('transactionContext', $options, false); $call = function ($resumeToken = null, $transaction = null) use ( @@ -483,7 +483,7 @@ public function read( * } * @return Transaction */ - public function transaction(Session $session, array $options = []) + public function transaction(Session $session, array $options = []): Transaction { $options += [ 'singleUse' => false, @@ -529,8 +529,11 @@ public function transaction(Session $session, array $options = []) * [Refer](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions) * @return Transaction */ - public function createTransaction(Session $session, array $res = [], array $options = []) - { + public function createTransaction( + Session $session, + array $res = [], + array $options = [] + ): Transaction { $res += [ 'id' => null ]; @@ -571,7 +574,7 @@ public function createTransaction(Session $session, array $res = [], array $opti * } * @return mixed */ - public function snapshot(Session $session, array $options = []) + public function snapshot(Session $session, array $options = []): TransactionalReadInterface { $options += [ 'singleUse' => false, @@ -605,8 +608,11 @@ public function snapshot(Session $session, array $options = []) * snapshot. **Defaults to** `Google\Cloud\Spanner\Snapshot`. * @return mixed */ - public function createSnapshot(Session $session, array $res = [], $className = Snapshot::class) - { + public function createSnapshot( + Session $session, + array $res = [], + $className = Snapshot::class + ): TransactionalReadInterface { $res += [ 'id' => null, 'readTimestamp' => null @@ -645,7 +651,7 @@ public function createSnapshot(Session $session, array $res = [], $className = S * } * @return Session */ - public function createSession($databaseName, array $options = []) + public function createSession($databaseName, array $options = []): Session { [$data, $callOptions] = $this->splitOptionalArgs($options); $data = [ @@ -677,7 +683,7 @@ public function createSession($databaseName, array $options = []) * @param string $sessionName The session's name. * @return Session */ - public function session($sessionName) + public function session($sessionName): Session { $sessionNameComponents = SpannerClient::parseName($sessionName); return new Session( @@ -728,8 +734,12 @@ public function session($sessionName) * } * @return QueryPartition[] */ - public function partitionQuery(Session $session, $transactionId, $sql, array $options = []) - { + public function partitionQuery( + Session $session, + $transactionId, + string $sql, + array $options = [] + ): array { // cache this to pass to the partition instance. $originalOptions = $options; [$data, $callOptions] = $this->splitOptionalArgs($options); @@ -792,7 +802,7 @@ public function partitionRead( KeySet $keySet, array $columns, array $options = [] - ) { + ): array { // cache this to pass to the partition instance. $originalOptions = $options; [$data, $callOptions] = $this->splitOptionalArgs($options); @@ -833,7 +843,7 @@ public function partitionRead( * @param array $options * @return array */ - private function partitionOptions(array &$options) + private function partitionOptions(array &$options): array { return array_filter([ 'partitionSizeBytes' => $this->pluck('partitionSizeBytes', $options, false), @@ -851,7 +861,7 @@ private function partitionOptions(array &$options) * * @return array */ - private function beginTransaction(Session $session, array $options = []) + private function beginTransaction(Session $session, array $options = []): array { [$data, $callOptions] = $this->splitOptionalArgs($options); $transactionOptions = $this->formatTransactionOptions( @@ -879,7 +889,7 @@ private function beginTransaction(Session $session, array $options = []) * @param KeySet $keySet The keySet object. * @return array [KeySet](https://cloud.google.com/spanner/reference/rpc/google.spanner.v1#keyset) */ - private function flattenKeySet(KeySet $keySet) + private function flattenKeySet(KeySet $keySet): array { $keys = $keySet->keySetObject(); @@ -900,7 +910,8 @@ private function flattenKeySet(KeySet $keySet) return $this->arrayFilterRemoveNull($keys); } - private function getDatabaseNameFromSession(Session $session) + + private function getDatabaseNameFromSession(Session $session): string { return $session->info()['databaseName']; } @@ -911,7 +922,7 @@ private function getDatabaseNameFromSession(Session $session) * @param array $mutations * @return array */ - private function serializeMutations(array $mutations) + private function serializeMutations(array $mutations): array { $serializedMutations = []; if (is_array($mutations)) { @@ -945,7 +956,7 @@ private function serializeMutations(array $mutations) * @param array $statements * @return array */ - private function formatStatements(array $statements) + private function formatStatements(array $statements): array { $result = []; foreach ($statements as $statement) { @@ -968,7 +979,7 @@ private function formatStatements(array $statements) * @param array $args * @return array */ - private function formatSqlParams(array $args) + private function formatSqlParams(array $args): array { $params = $this->pluck('params', $args); if ($params) { @@ -985,7 +996,7 @@ private function formatSqlParams(array $args) * * @return array */ - private function createTransactionSelector(array &$args, ?string $transactionId = null) + private function createTransactionSelector(array &$args, ?string $transactionId = null): array { $transactionSelector = []; if (isset($args['transaction'])) { @@ -1012,7 +1023,7 @@ private function createTransactionSelector(array &$args, ?string $transactionId * * @return array */ - private function createQueryOptions(array $args) + private function createQueryOptions(array $args): array { $queryOptions = $this->pluck('queryOptions', $args, false) ?: []; // Query options precedence is query-level, then environment-level, then client-level. @@ -1025,6 +1036,7 @@ private function createQueryOptions(array $args) $queryOptions += ['optimizerStatisticsPackage' => $envQueryOptimizerStatisticsPackage]; } $queryOptions += $this->defaultQueryOptions ?: []; + return $queryOptions; } @@ -1032,7 +1044,7 @@ private function createQueryOptions(array $args) * @param array $transactionOptions * @return array */ - private function formatTransactionOptions(array $transactionOptions) + private function formatTransactionOptions(array $transactionOptions): array { if (isset($transactionOptions['readOnly'])) { $ro = $transactionOptions['readOnly']; @@ -1074,10 +1086,9 @@ private function executeStreamingSql(array $args) * @param array $args * @return \Generator */ - private function streamingRead(array $args) + private function streamingRead(array $args): \Generator { list($data, $callOptions) = $this->splitOptionalArgs($args); - // $data['keySet'] = ($this->pluck('keySet', $data); $data['transaction'] = $this->createTransactionSelector($data); $callOptions = $this->conditionallyUnsetLarHeader($callOptions, $this->routeToLeader); $databaseName = $this->pluck('database', $data); @@ -1086,6 +1097,7 @@ private function streamingRead(array $args) $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); $response = $this->spannerClient->streamingRead($request, $callOptions); + return $this->handleResponse($response); } @@ -1093,7 +1105,7 @@ private function streamingRead(array $args) * @param array $args * @return array */ - private function formatSingleUseTransactionOptions(array $args) + private function formatSingleUseTransactionOptions(array $args): array { // Internal flag, need to unset before passing to serializer unset($args['singleUse']); @@ -1102,6 +1114,7 @@ private function formatSingleUseTransactionOptions(array $args) // request ignores singleUseTransaction even if the transactionId is set to null unset($args['transactionId']); } + return $args; } @@ -1111,12 +1124,13 @@ private function formatSingleUseTransactionOptions(array $args) * * @return array */ - private function formatPartitionQueryOptions(array $args) + private function formatPartitionQueryOptions(array $args): array { $parameters = $this->pluck('parameters', $args, false) ?: []; $types = $this->pluck('types', $args, false) ?: []; $args += $this->mapper->formatParamsForExecuteSql($parameters, $types); $args = $this->formatSqlParams($args); + return $args; } @@ -1130,7 +1144,7 @@ private function formatPartitionQueryOptions(array $args) private function conditionallyUnsetLarHeader( array $args, bool $value = true - ) { + ): array { if (!$value) { unset($args['headers'][$this->larHeader]); } diff --git a/Spanner/src/RequestTrait.php b/Spanner/src/RequestTrait.php index 9c5e61d82e2a..1e54838dfda5 100644 --- a/Spanner/src/RequestTrait.php +++ b/Spanner/src/RequestTrait.php @@ -82,20 +82,20 @@ private function addResourcePrefixHeader(array $args, string $value) * @param callable $call The GAPIC client and method for the list operations request * @param Message $request The list operations request * @param array $callOptions [optional] Call options for the request - * @param callable $operationResponseMapper [optional] A callable to map the Operation to an + * @param callable $resultMapper [optional] A callable to map the Operation to an * operation response. Defaults to `$this->resumeOperation()`. * @return ItemIterator<OperationResponse> */ private function buildLongRunningIterator( callable $call, Message $request, - array $callOptions = [], - ?callable $operationResponseMapper = null + array $callOptions, + ?callable $resultMapper = null ): ItemIterator { $resultLimit = $this->pluck('resultLimit', $callOptions, false) ?: 0; return new ItemIterator( new PageIterator( - $operationResponseMapper ?: function (Operation $operation) { + $resultMapper ?: function (Operation $operation) { return $this->resumeOperation( $operation->getName(), ['lastProtoResponse' => $operation] @@ -124,4 +124,34 @@ function (array $args) use ($call) { ) ); } + + private function buildListItemsIterator( + callable $call, + Message $request, + array $callOptions, + callable $resultMapper, + string $itemsKey, + ?int $resultLimit = null + ) { + return new ItemIterator( + new PageIterator( + $resultMapper, + function ($args) use ($call) { + if ($pageToken = $this->pluck('pageToken', $args, false) ?: null) { + $args['request']->setPageToken($pageToken); + } + $response = $call($args['request'], $args['callOptions']); + return $this->handleResponse($response); + }, + [ + 'request' => $request, + 'callOptions' => $callOptions + ], + [ + 'itemsKey' => $itemsKey, + 'resultLimit' => $resultLimit + ] + ) + ); + } } diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index e5fa9d6dddae..6143f03da4ec 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -469,29 +469,18 @@ public function instanceConfigurations(array $options = []) [$data, $callOptions] = $this->splitOptionalArgs($options); $data['parent'] = $this->projectName; - $resultLimit = $this->pluck('resultLimit', $options, false) ?: 0; - return new ItemIterator( - new PageIterator( - function (array $config) { - return $this->instanceConfiguration($config['name'], $config); - }, - function ($callOptions) use ($data) { - if (isset($callOptions['pageToken'])) { - $data['pageToken'] = $callOptions['pageToken']; - } + $request = $this->serializer->decodeMessage(new ListInstanceConfigsRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - $request = $this->serializer->decodeMessage(new ListInstanceConfigsRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - - $response = $this->instanceAdminClient->listInstanceConfigs($request, $callOptions); - return $this->handleResponse($response); - }, - $callOptions, - [ - 'itemsKey' => 'instanceConfigs', - 'resultLimit' => $resultLimit - ] - ) + return $this->buildListItemsIterator( + [$this->instanceAdminClient, 'listInstanceConfigs'], + $request, + $callOptions, + function (array $config) { + return $this->instanceConfiguration($config['name'], $config); + }, + 'instanceConfigs', + $this->pluck('resultLimit', $options, false) ); } @@ -668,30 +657,19 @@ public function instances(array $options = []) [$data, $callOptions] = $this->splitOptionalArgs($options); $data += ['filter' => '', 'parent' => $this->projectName]; - $resultLimit = $this->pluck('resultLimit', $data, false); - return new ItemIterator( - new PageIterator( - function (array $instance) { - $name = InstanceAdminClient::parseName($instance['name'])['instance']; - return $this->instance($name, $instance); - }, - function ($callOptions) use ($data) { - if (isset($callOptions['pageToken'])) { - $data['pageToken'] = $callOptions['pageToken']; - } + $request = $this->serializer->decodeMessage(new ListInstancesRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - $request = $this->serializer->decodeMessage(new ListInstancesRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - - $response = $this->instanceAdminClient->listInstances($request, $callOptions); - return $this->handleResponse($response); - }, - $callOptions, - [ - 'itemsKey' => 'instances', - 'resultLimit' => $resultLimit - ] - ) + return $this->buildListItemsIterator( + [$this->instanceAdminClient, 'listInstances'], + $request, + $callOptions, + function (array $instance) { + $name = InstanceAdminClient::parseName($instance['name'])['instance']; + return $this->instance($name, $instance); + }, + 'instances', + $this->pluck('resultLimit', $options, false) ); } diff --git a/Spanner/test.php b/Spanner/test.php deleted file mode 100644 index 67163d1f0884..000000000000 --- a/Spanner/test.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -require 'vendor/autoload.php'; - -use Google\Cloud\Spanner\SpannerClient; -use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; -use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; -use Google\LongRunning\ListOperationsRequest; - -// $dbAdmin = new DatabaseAdminClient(); -// $operationsClient = $dbAdmin->getOperationsClient(); -// $request = new ListOperationsRequest(); -// // $request->setName('projects/php-docs-samples-kokoro/operations'); -// $operationsClient->listOperations($request); -// // exit; - -// var_dump(InstanceAdminClient::instanceConfigName('my-project', '')); -// exit; -$spanner = new SpannerClient(); -$config = $spanner->instanceConfiguration('regional-us-central1'); -$config->create(); - -foreach ($spanner->instanceConfigOperations() as $instanceConfig) { - var_dump($instanceConfig->getName()); -} - -// // $spanner->longRunningOperations(); -// foreach ($spanner->instances() as $instance) { -// foreach ($instance->databases() as $database) { -// foreach ($database->longRunningOperations() as $operation) { -// var_dump($operation->getName()); -// } -// // var_dump($operation->getName()); -// } -// } - diff --git a/Spanner/tests/Unit/TransactionTypeTest.php b/Spanner/tests/Unit/TransactionTypeTest.php index 5b672f81017f..cd631d1f072a 100644 --- a/Spanner/tests/Unit/TransactionTypeTest.php +++ b/Spanner/tests/Unit/TransactionTypeTest.php @@ -83,7 +83,7 @@ public function setUp(): void $time = \DateTime::createFromFormat('U', time()); $nanos = 500000005; - $this->timestamp = (new Timestamp($time, $nanos))->formatAsString(); + $this->timestamp = new Timestamp($time, $nanos); $this->protoTimestamp = new TimestampProto(['seconds' => $time->format('U'), 'nanos' => $nanos]); $this->spannerClient = $this->prophesize(SpannerClient::class); @@ -256,9 +256,6 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch { $seconds = 1; $nanos = 2; - - $time = $this->parseTimeString($this->timestamp); - $timestamp = new Timestamp($time[0], $time[1]); $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); @@ -266,7 +263,7 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'minReadTimestamp' => ['seconds' => $time[0]->format('U'), 'nanos' => $time[1]], + 'minReadTimestamp' => $this->protoTimestamp->__debugInfo(), 'maxStaleness' => $duration, ] ] @@ -283,7 +280,7 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ 'singleUse' => true, - 'minReadTimestamp' => $timestamp, + 'minReadTimestamp' => $this->timestamp, 'maxStaleness' => $duration ]); @@ -294,9 +291,6 @@ public function testDatabasePreAllocatedSnapshotMinReadTimestamp() { $this->expectException(\BadMethodCallException::class); - $time = $this->parseTimeString($this->timestamp); - $timestamp = new Timestamp($time[0], $time[1]); - $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); $this->spannerClient->executeStreamingSql(Argument::cetera())->shouldNotBeCalled(); $this->spannerClient->deleteSession(Argument::cetera())->shouldNotBeCalled(); @@ -304,7 +298,7 @@ public function testDatabasePreAllocatedSnapshotMinReadTimestamp() $database = $this->database($this->spannerClient->reveal()); $snapshot = $database->snapshot([ - 'minReadTimestamp' => $timestamp, + 'minReadTimestamp' => $this->timestamp, ]); } @@ -314,7 +308,6 @@ public function testDatabasePreAllocatedSnapshotMaxStaleness() $seconds = 1; $nanos = 2; - $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); @@ -335,14 +328,12 @@ public function testDatabaseSnapshotSingleUseReadTimestampAndExactStaleness($chu { $seconds = 1; $nanos = 2; - - $time = $this->parseTimeString($this->timestamp); - $timestamp = new Timestamp($time[0], $time[1]); $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); + $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'readTimestamp' => $this->formatTimestampForApi($this->timestamp), + 'readTimestamp' => $this->protoTimestamp->__debugInfo(), 'exactStaleness' => $duration, ] ] @@ -362,7 +353,7 @@ public function testDatabaseSnapshotSingleUseReadTimestampAndExactStaleness($chu $snapshot = $database->snapshot([ 'singleUse' => true, - 'readTimestamp' => $timestamp, + 'readTimestamp' => $this->timestamp, 'exactStaleness' => $duration ]); @@ -377,12 +368,10 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c $seconds = 1; $nanos = 2; - $time = $this->parseTimeString($this->timestamp); - $timestamp = new Timestamp($time[0], $time[1]); $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); $options = [ 'readOnly' => [ - 'readTimestamp' => $this->formatTimestampForApi($this->timestamp), + 'readTimestamp' => $this->protoTimestamp->__debugInfo(), 'exactStaleness' => $duration, ] ]; @@ -419,7 +408,7 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c $database = $this->database($this->spannerClient->reveal(), $serializer); $snapshot = $database->snapshot([ - 'readTimestamp' => $timestamp, + 'readTimestamp' => $this->timestamp, 'exactStaleness' => $duration ]); From e6e835a0a633cec1f5af0994b9f492b7f5d351d7 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 18 Nov 2024 21:23:28 +0000 Subject: [PATCH 03/12] comment out code for debugging --- .github/workflows/system-tests-spanner-emulator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/system-tests-spanner-emulator.yaml b/.github/workflows/system-tests-spanner-emulator.yaml index ffde133af921..299920ca5be6 100644 --- a/.github/workflows/system-tests-spanner-emulator.yaml +++ b/.github/workflows/system-tests-spanner-emulator.yaml @@ -49,7 +49,7 @@ jobs: - name: Install dependencies run: | # ensure composer uses local Core instead of pulling from packagist - composer config repositories.local --json '{"type":"path", "url": "../Core", "options": {"versions": {"google/cloud-core": "1.100"}}}' -d Spanner + # composer config repositories.local --json '{"type":"path", "url": "../Core", "options": {"versions": {"google/cloud-core": "1.100"}}}' -d Spanner composer update --prefer-dist --no-interaction --no-suggest -d Spanner/ - name: Run system tests From f63b4702d6bbca37e99ccd5e1479bb09bb56fa15 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 18 Nov 2024 16:42:19 -0800 Subject: [PATCH 04/12] snippet test fixing --- Spanner/src/Database.php | 4 +- Spanner/tests/ResultGeneratorTrait.php | 129 ++-- Spanner/tests/Snippet/BackupTest.php | 57 +- .../tests/Snippet/Batch/BatchClientTest.php | 177 +++--- .../tests/Snippet/Batch/BatchSnapshotTest.php | 127 ++-- .../Snippet/Batch/QueryPartitionTest.php | 47 +- .../tests/Snippet/Batch/ReadPartitionTest.php | 48 +- Spanner/tests/Snippet/BatchDmlResultTest.php | 43 +- Spanner/tests/Snippet/CommitTimestampTest.php | 46 +- Spanner/tests/Snippet/DatabaseTest.php | 552 ++++++++++-------- Spanner/tests/Snippet/DateTest.php | 1 + .../Snippet/InstanceConfigurationTest.php | 80 +-- Spanner/tests/Snippet/InstanceTest.php | 194 +++--- Spanner/tests/Snippet/ResultTest.php | 4 +- Spanner/tests/Snippet/SnapshotTest.php | 4 +- Spanner/tests/Snippet/SpannerClientTest.php | 1 + Spanner/tests/Snippet/StructTypeTest.php | 6 +- Spanner/tests/Snippet/StructValueTest.php | 6 +- Spanner/tests/Snippet/TransactionTest.php | 70 +-- .../Snippet/TransactionalReadMethodsTest.php | 39 +- Spanner/tests/Unit/DatabaseTest.php | 8 +- 21 files changed, 913 insertions(+), 730 deletions(-) diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index d43a83ed3cf8..3fe0fb3ed557 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -2335,7 +2335,7 @@ public function databaseOperations(array $options = []): ItemIterator * * Example: * ``` - * $operation = $spanner->resumeOperation($operationName); + * $operation = $database->resumeOperation($operationName); * ``` * * @param string $operationName The Long Running Operation name. @@ -2355,7 +2355,7 @@ public function resumeOperation($operationName, array $options = []) * * Example: * ``` - * $operations = $backup->longRunningOperations(); + * $operations = $database->longRunningOperations(); * ``` * * @param array $options [optional] { diff --git a/Spanner/tests/ResultGeneratorTrait.php b/Spanner/tests/ResultGeneratorTrait.php index a6cd118facc0..9b13bc49ebe8 100644 --- a/Spanner/tests/ResultGeneratorTrait.php +++ b/Spanner/tests/ResultGeneratorTrait.php @@ -34,95 +34,78 @@ */ trait ResultGeneratorTrait { - /** - * Yield rows with user-specified data. - * - * @param array[] $rows A list of arrays containing `name`, `type` and `value` keys. - * @param ResultSetStats|null $stats If true, statistics will be included. - * **Defaults to** `false`. - * @param string|null $transactionId If set, the value will be included as the - * transaction ID. **Defaults to** `null`. - * @return \Generator - */ - private function yieldRows(array $rows, $stats = null, $transactionId = null) - { - $fields = []; - $values = []; - foreach ($rows as $row) { - $fields[] = new Field([ - 'name' => $row['name'], - 'type' => new Type(['code' => $row['type']]) - ]); - - $values[] = new Value(['string_value' => $row['value']]); - } - - $result = [ - 'metadata' => new ResultSetMetadata([ - 'row_type' => new StructType([ - 'fields' => $fields - ]) - ]), - 'values' => $values - ]; - - if ($stats) { - $result['stats'] = $stats; - } - - if ($transactionId) { - $result['metadata']->setTransaction(new Transaction(['id' => $transactionId])); - } - - if (isset($result['stats'])) { - $result['stats'] = $stats; - } - - yield new PartialResultSet($result); - } - - /** - * Yield the given array as a generator. - * - * @param array $data The input data - * @return \Generator - */ - private function resultGeneratorData(array $data) - { - yield $data; - } - private function resultGeneratorStream( array $chunks = null, ResultSetStats $stats = null, string $transactionId = null ) { $this->stream = $this->prophesize(ServerStream::class); + $chunks = $chunks ?: [ + [ + 'name' => 'ID', + 'type' => Database::TYPE_INT64, + 'value' => '10' + ] + ]; + + $rows = []; + if ($chunks) { foreach ($chunks as $i => $chunk) { - $result = new PartialResultSet(); - $result->mergeFromJsonString($chunk); - $chunks[$i] = $result; + if (is_string($chunk)) { + // merge from JSON string + $result = new PartialResultSet(); + $result->mergeFromJsonString($chunk); + $rows[$i] = $result; + } elseif ($chunk instanceof PartialResultSet) { + $rows[$i] = $chunk; + } + } + } + + if (!$rows) { + $fields = []; + $values = []; + foreach ($chunks as $row) { + $fields[] = new Field([ + 'name' => $row['name'], + 'type' => new Type(['code' => $row['type']]) + ]); + + $values[] = new Value(['string_value' => $row['value']]); } - $this->stream->readAll() - ->willReturn($this->resultGeneratorChunks($chunks)); - } else { - $rows = [ - [ - 'name' => 'ID', - 'type' => Database::TYPE_INT64, - 'value' => '10' - ] + $result = [ + 'metadata' => new ResultSetMetadata([ + 'row_type' => new StructType([ + 'fields' => $fields + ]) + ]), + 'values' => $values ]; - $this->stream->readAll() - ->willReturn($this->yieldRows($rows, $stats, $transactionId)); + + if ($stats) { + $result['stats'] = $stats; + } + + if ($transactionId) { + $result['metadata']->setTransaction(new Transaction(['id' => $transactionId])); + } + + if (isset($result['stats'])) { + $result['stats'] = $stats; + } + + $rows[] = new PartialResultSet($result); } + $this->stream->readAll() + ->willReturn($this->resultGeneratorArray($rows)); + return $this->stream->reveal(); } - private function resultGeneratorChunks($chunks) + private function resultGeneratorArray($chunks) { foreach ($chunks as $chunk) { yield $chunk; diff --git a/Spanner/tests/Snippet/BackupTest.php b/Spanner/tests/Snippet/BackupTest.php index 3dff343dc807..52fbae8d5b93 100644 --- a/Spanner/tests/Snippet/BackupTest.php +++ b/Spanner/tests/Snippet/BackupTest.php @@ -41,6 +41,7 @@ use Google\LongRunning\ListOperationsRequest; use Google\LongRunning\ListOperationsResponse; use Google\LongRunning\Operation; +use Google\Protobuf\Timestamp as TimestampProto; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -59,8 +60,9 @@ class BackupTest extends SnippetTestCase const BACKUP = 'my-backup'; private $serializer; + private $operationResponse; + private $databaseAdminClient; private $backup; - private $spanner; private $instance; private $expireTime; @@ -71,7 +73,11 @@ public function setUp(): void $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); $this->serializer = new Serializer(); - $this->spanner = new SpannerClient(['projectId' => 'my-project']); + + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + $this->expireTime = new \DateTime('+ 7 hours'); $database = $this->prophesize(Database::class); $database->name()->willReturn(DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE)); @@ -110,28 +116,41 @@ public function testCreate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - // $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } - // public function testCreateCopy() - // { - // $snippet = $this->snippetFromMethod(Backup::class, 'createCopy'); - // $snippet->addLocal('spanner', $this->spanner); + public function testCreateCopy() + { + $sourceInstance = $this->prophesize(Instance::class); + $destInstance = $this->prophesize(Instance::class); + $sourceInstance->backup('source-backup-id') + ->shouldBeCalledOnce() + ->willReturn($this->backup); + $destInstance->backup('new-backup-id') + ->shouldBeCalledOnce() + ->willReturn($this->backup); + + $spanner = $this->prophesize(SpannerClient::class); + $spanner->instance('source-instance-id')->willReturn($sourceInstance); + $spanner->instance('destination-instance-id')->willReturn($destInstance); - // $this->databaseAdminClient->copyBackup( - // Argument::type(CopyBackupRequest::class), - // Argument::type('array') - // ) - // ->shouldBeCalledOnce() - // ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + $snippet = $this->snippetFromMethod(Backup::class, 'createCopy'); + $snippet->addLocal('spanner', $spanner->reveal()); + + $this->databaseAdminClient->copyBackup( + Argument::type(CopyBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); - // $res = $snippet->invoke('operation'); - // $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - // } + $res = $snippet->invoke('operation'); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + } public function testDelete() { @@ -237,7 +256,7 @@ public function testUpdateExpireTime() { $backup = [ 'name' => 'foo', - 'expire_time' => new \Google\Protobuf\Timestamp(['seconds' => $this->expireTime->format('U')]) + 'expire_time' => new TimestampProto(['seconds' => $this->expireTime->format('U')]) ]; $snippet = $this->snippetFromMethod(Backup::class, 'updateExpireTime'); @@ -262,7 +281,7 @@ public function testUpdateExpireTime() public function testResumeOperation() { $snippet = $this->snippetFromMagicMethod(Backup::class, 'resumeOperation'); - $snippet->addLocal('spanner', $this->spanner); + $snippet->addLocal('spanner', new SpannerClient(['projectId' => 'my-project'])); $snippet->addLocal('backup', $this->backup); $snippet->addLocal('operationName', 'foo'); diff --git a/Spanner/tests/Snippet/Batch/BatchClientTest.php b/Spanner/tests/Snippet/Batch/BatchClientTest.php index fc02fd1dcac1..c08cf9096dd0 100644 --- a/Spanner/tests/Snippet/Batch/BatchClientTest.php +++ b/Spanner/tests/Snippet/Batch/BatchClientTest.php @@ -22,9 +22,11 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\PubSub\PubSubClient; -use Google\Cloud\PubSub\V1\Client\PublisherClient; -use Google\Cloud\PubSub\V1\Client\SubscriberClient; +use Google\Cloud\PubSub\Topic; +use Google\Cloud\PubSub\Message; +use Google\Cloud\PubSub\Subscription; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\QueryPartition; @@ -32,6 +34,17 @@ use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\Session as SessionProto; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionResponse; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\Transaction as TransactionProto; +use Google\Protobuf\Timestamp as TimestampProto; +use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; /** @@ -40,7 +53,9 @@ */ class BatchClientTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; + use ResultGeneratorTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; @@ -54,9 +69,10 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $this->serializer = new Serializer(); $this->client = new BatchClient( - new Operation($this->requestHandler->reveal(), $this->serializer, false), + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE ); } @@ -98,59 +114,46 @@ public function testPubSubExample() $message2 = $message1; $message2['attributes']['partition'] = $partition2->serialize(); - if (!property_exists(PubSubClient::class, 'requestHandler')) { - $this->markTestSkipped("Skipping testPubSubExample test as property 'requestHandler' is missing"); - } - // setup pubsub service call stubs - $pubsub = TestHelpers::stub(PubSubClient::class, [['projectId' => 'test']], ['requestHandler']); - $requestHandler = $this->prophesize(RequestHandler::class); - $requestHandler->sendRequest( - PublisherClient::class, - 'publish', - Argument::cetera() - )->shouldBeCalled() - ->will(function () use ($requestHandler) { - $requestHandler->sendRequest( - PublisherClient::class, - 'publish', - Argument::cetera() - )->shouldBeCalled(); - }); - - $requestHandler->sendRequest( - SubscriberClient::class, - 'pull', - Argument::cetera() - )->shouldBeCalled() - ->willReturn([ - 'receivedMessages' => [ - [ - 'message' => [ - 'attributes' => [ - 'snapshot' => $snapshotString, - 'partition' => $partition1->serialize() - ] + $topic = $this->prophesize(Topic::class); + $topic->publish(Argument::cetera()) + ->shouldBeCalledTimes(2); + $pubsub = $this->prophesize(PubSubClient::class); + $pubsub->topic(Argument::cetera()) + ->shouldBeCalled() + ->willReturn($topic->reveal()); + + $subscription = $this->prophesize(Subscription::class); + $subscription->pull(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn([ + new Message([ + 'attributes' => [ + 'snapshot' => $snapshotString, + 'partition' => $partition1->serialize() ] - ] - ] - ]); + ]) + ]); + $pubsub->subscription(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn($subscription->reveal()); // setup spanner service call stubs $this->spannerClient->partitionQuery( - null, - [ + Argument::type(PartitionQueryRequest::class), + Argument::type('array') + )->willReturn(new PartitionResponse([ 'partitions' => [ - ['partitionToken' => $partition1->token()], - ['partitionToken' => $partition2->token()] + new Partition(['partition_token' => $partition1->token()]), + new Partition(['partition_token' => $partition2->token()]), ] - ] + ]) ); $this->spannerClient->executeStreamingSql( - function ($args) use ($partition1) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) use ($partition1) { + $message = $this->serializer->encodeMessage($request); $this->assertEquals( $message['partitionToken'], $partition1->token() @@ -161,50 +164,62 @@ function ($args) use ($partition1) { ); $this->assertEquals($message['session'], self::SESSION); return true; - }, - $this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'loginCount', - 'type' => [ - 'code' => Database::TYPE_INT64 + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'loginCount', + 'type' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] + ], + 'values' => [ + ['numberValue' => 0] ] - ], - 'values' => [0] - ]) - ); + ] + )])); + $this->spannerClient->createSession( - null, - ['name' => self::SESSION] - ); + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ] - ); + 'read_timestamp' => new TimestampProto(['seconds' => $time]) + ])); - $this->mockSendRequest(SpannerClient::class, 'deleteSession', null, null); + $this->spannerClient->deleteSession( + Argument::type(DeleteSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); // inject clients $publisher->addLocal('batch', $this->client); - $publisher->addLocal('pubsub', $pubsub); + $publisher->addLocal('pubsub', $pubsub->reveal()); $publisher->replace('$pubsub = new PubSubClient();', ''); $publisher->insertAfterLine(0, 'function areWorkersDone() { return true; }'); $subscriber->addLocal('batch', $this->client); - $subscriber->addLocal('pubsub', $pubsub); + $subscriber->addLocal('pubsub', $pubsub->reveal()); $subscriber->replace('$pubsub = new PubSubClient();', ''); $publisher->insertAfterLine(0, 'function processResult($res) {iterator_to_array($res);}'); - $this->refreshOperation($this->client, $this->requestHandler->reveal(), $this->serializer); $publisher->invoke(); - $subscriber->invoke(); } @@ -216,19 +231,17 @@ public function testSnapshot() $time = time(); $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ] - ); + 'read_timestamp' => new TimestampProto(['seconds' => $time]) + ])); $this->spannerClient->createSession( - null, - [ - 'name' => self::SESSION - ] - ); - $this->refreshOperation($this->client, $this->requestHandler->reveal(), $this->serializer); + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); $res = $snippet->invoke('snapshot'); $this->assertInstanceOf(BatchSnapshot::class, $res->returnVal()); diff --git a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php index 2ef49adafb54..7f1e773981b3 100644 --- a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php +++ b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php @@ -20,6 +20,7 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\PartitionInterface; @@ -31,7 +32,19 @@ use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\Session as SessionProto; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\PartitionReadRequest; +use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionResponse; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Transaction; +use Google\Protobuf\Timestamp as TimestampProto; use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -41,6 +54,7 @@ class BatchSnapshotTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; + use ResultGeneratorTrait; const DATABASE = 'projects/my-awesome-project/instances/my-instance/databases/my-database'; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; @@ -57,6 +71,7 @@ public function setUp(): void $this->checkAndSkipGrpcTests(); $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $sessData = SpannerClient::parseName(self::SESSION, 'session'); $this->session = $this->prophesize(Session::class); @@ -67,37 +82,38 @@ public function setUp(): void ]); $this->time = time(); - $this->snapshot = TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->requestHandler->reveal(), $this->serializer, false), + $this->snapshot = new BatchSnapshot( + new Operation($this->spannerClient->reveal(), $this->serializer, false), $this->session->reveal(), [ 'id' => self::TRANSACTION, 'readTimestamp' => new Timestamp(\DateTime::createFromFormat('U', (string) $this->time)) ] - ], ['operation', 'session']); + ); } public function testClass() { $this->spannerClient->createSession( - null, - ['name' => self::SESSION] - ); + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new Transaction([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat( - 'U', - (string) $this->time - )->format(Timestamp::FORMAT) - ] - ); + 'read_timestamp' => new TimestampProto([ + 'seconds' => $this->time + ]) + ])); - $client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->requestHandler->reveal(), $this->serializer, false), + $client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ]); + ); $snippet = $this->snippetFromClass(BatchSnapshot::class); $snippet->setLine(3, ''); @@ -136,33 +152,44 @@ public function testClose() $res = $snippet->invoke(); } - /** - * @dataProvider providePartitionMethods - */ - public function testPartitionRead($method) + public function testPartitionRead() { - $this->mockSendRequest( - SpannerClient::class, - $method, - null, - [ + $this->spannerClient->partitionRead( + Argument::type(PartitionReadRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new PartitionResponse([ 'partitions' => [ - ['partitionToken' => 'foo'] + new Partition(['partition_token' => 'foo']) ] - ] - ); - $this->refreshOperation($this->snapshot, $this->requestHandler->reveal(), $this->serializer); + ])); - $snippet = $this->snippetFromMethod(BatchSnapshot::class, $method); + $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'partitionRead'); $snippet->addLocal('snapshot', $this->snapshot); $res = $snippet->invoke('partitions'); $this->assertContainsOnlyInstancesOf(PartitionInterface::class, $res->returnVal()); } - public function providePartitionMethods() + public function testPartitionQuery() { - return [['partitionRead'], ['partitionQuery']]; + $this->spannerClient->partitionQuery( + Argument::type(PartitionQueryRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new PartitionResponse([ + 'partitions' => [ + new Partition(['partition_token' => 'foo']) + ] + ])); + + $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'partitionQuery'); + $snippet->addLocal('snapshot', $this->snapshot); + + $res = $snippet->invoke('partitions'); + $this->assertContainsOnlyInstancesOf(PartitionInterface::class, $res->returnVal()); } public function testExecutePartition() @@ -173,24 +200,30 @@ public function testExecutePartition() $partition = new QueryPartition($token, $sql, $opts); $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'loginCount', - 'type' => [ - 'code' => Database::TYPE_INT64 + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'loginCount', + 'type' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] + ], + 'values' => [ + ['numberValue' => 0] ] - ], - 'values' => [0] - ]) - ); - $this->refreshOperation($this->snapshot, $this->requestHandler->reveal(), $this->serializer); + ] + )])); $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'executePartition'); $snippet->addLocal('snapshot', $this->snapshot); diff --git a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php index 18ddd97e25ab..bb6db70a170c 100644 --- a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php @@ -22,8 +22,20 @@ use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\QueryPartition; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\Session as SessionProto; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionResponse; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\Transaction; +use Google\Protobuf\Timestamp as TimestampProto; +use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -31,6 +43,7 @@ */ class QueryPartitionTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; use PartitionSharedSnippetTestTrait; @@ -48,6 +61,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $this->serializer = new Serializer(); $this->time = time(); $this->partition = new QueryPartition($this->token, $this->sql, $this->options); @@ -56,29 +70,32 @@ public function setUp(): void public function testClass() { $this->spannerClient->createSession( - null, - ['name' => self::SESSION] - ); + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new Transaction([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ] + 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) + ]) ); $this->spannerClient->partitionQuery( - null, - [ + Argument::type(PartitionQueryRequest::class), + Argument::type('array') + )->willReturn(new PartitionResponse([ 'partitions' => [ - ['partitionToken' => 'foo'] + new Partition(['partition_token' => 'foo']) ] - ] - ); + ])); - $client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->requestHandler->reveal(), $this->serializer, false), + $client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ]); + ); $snippet = $this->snippetFromClass(QueryPartition::class); $snippet->setLine(3, ''); diff --git a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php index e9b288e84ba2..df076c4e51c7 100644 --- a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php @@ -22,9 +22,21 @@ use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\ReadPartition; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\Session as SessionProto; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\PartitionReadRequest; +use Google\Cloud\Spanner\V1\PartitionResponse; +use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\Transaction; +use Google\Protobuf\Timestamp as TimestampProto; +use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -32,6 +44,7 @@ */ class ReadPartitionTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; use PartitionSharedSnippetTestTrait { provideGetters as private getters; @@ -53,6 +66,7 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $this->serializer = new Serializer(); $this->time = time(); $this->table = 'table'; @@ -64,31 +78,33 @@ public function setUp(): void public function testClass() { $this->spannerClient->createSession( - null, - ['name' => self::SESSION] - ); + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new Transaction([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ] + 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) + ]) ); $this->spannerClient->partitionRead( - null, - [ + Argument::type(PartitionReadRequest::class), + Argument::type('array') + )->willReturn(new PartitionResponse([ 'partitions' => [ - ['partitionToken' => 'foo'] + new Partition(['partition_token' => 'foo']) ] ] - ); + )); - $client = TestHelpers::stub(BatchClient::class, [ - new Operation($this->requestHandler->reveal(), $this->serializer, false), + $client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ], [ - 'operation' - ]); + ); $snippet = $this->snippetFromClass(ReadPartition::class); $snippet->setLine(4, ''); diff --git a/Spanner/tests/Snippet/BatchDmlResultTest.php b/Spanner/tests/Snippet/BatchDmlResultTest.php index 7ad12585b287..24df03f8e9f9 100644 --- a/Spanner/tests/Snippet/BatchDmlResultTest.php +++ b/Spanner/tests/Snippet/BatchDmlResultTest.php @@ -24,8 +24,18 @@ use Google\Cloud\Spanner\BatchDmlResult; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; +use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\Transaction as TransactionProto; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; +use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; +use Google\Protobuf\Timestamp as TimestampProto; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -47,6 +57,7 @@ public function setUp(): void $this->checkAndSkipGrpcTests(); $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $this->result = new BatchDmlResult([ 'resultSets' => [ [ @@ -70,21 +81,22 @@ public function setUp(): void public function testClass() { $this->spannerClient->executeBatchDml( - null, - ['resultSets' => []] - ); + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse(['result_sets' => []])); $this->spannerClient->beginTransaction( - null, - ['id' => 'id'] - ); + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => 'id'])); $this->spannerClient->commit( - null, - [ - 'commitTimestamp' => $this->formatTimeAsString(new \DateTime(), 0) - ] - ); + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $session = $this->prophesize(Session::class); $session->name()->willReturn( @@ -106,14 +118,17 @@ public function testClass() $instance->name()->willReturn('projects/test-project/instances/my-instance'); $instance->directedReadOptions()->willReturn([]); - $database = TestHelpers::stub(Database::class, [ - $this->requestHandler->reveal(), + $databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + + $database = new Database( + $this->spannerClient->reveal(), + $databaseAdminClient->reveal(), $this->serializer, $instance->reveal(), 'test-project', 'projects/test-project/instances/my-instance/databases/my-database', $sessionPool->reveal() - ]); + ); $snippet = $this->snippetFromClass(BatchDmlResult::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); diff --git a/Spanner/tests/Snippet/CommitTimestampTest.php b/Spanner/tests/Snippet/CommitTimestampTest.php index 98bc87449e33..9fc76a206055 100644 --- a/Spanner/tests/Snippet/CommitTimestampTest.php +++ b/Spanner/tests/Snippet/CommitTimestampTest.php @@ -24,8 +24,14 @@ use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\Session; use Google\Cloud\Spanner\V1\DeleteSessionRequest; use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Protobuf\Timestamp as TimestampProto; +use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -33,6 +39,7 @@ */ class CommitTimestampTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; const SESSION = 'projects/my-awesome-project/instances/my-instance/databases/my-database/sessions/session-id'; @@ -43,6 +50,7 @@ class CommitTimestampTest extends SnippetTestCase public function setUp(): void { $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(GapicSpannerClient::class); $this->checkAndSkipGrpcTests(); } @@ -50,22 +58,20 @@ public function testClass() { $id = 'abc'; - $client = new SpannerClient( - [['projectId' => 'my-project']], - ['requestHandler', 'serializer'] - ); - - $this->GapicSpannerClient->createSession( + $this->spannerClient->createSession( Argument::type(CreateSessionRequest::class), Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new CreateSessionResponse(['name' => self::SESSION])); - $this->GapicSpannerClient->deleteSession( + ->willReturn(new Session(['name' => self::SESSION])); + $this->spannerClient->deleteSession( Argument::type(DeleteSessionRequest::class), Argument::type('array') ) ->shouldBeCalledOnce(); + $this->spannerClient->addMiddleware(Argument::type('callable')) + ->shouldBeCalledOnce(); + $mutation = [ 'insert' => [ 'table' => 'myTable', @@ -73,21 +79,23 @@ public function testClass() 'values' => [[$id, CommitTimestamp::SPECIAL_VALUE]] ] ]; - $this->GapicSpannerClient->commit( - Argument::type(CommitRequest::class), + $this->spannerClient->commit( + Argument::that(function (CommitRequest $request) use ($mutation) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals($message['mutations'][0], $mutation); + return true; + }), Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(function ($args) use ($mutation) { - $message = $this->serializer->encodeMessage($args); - $this->assertEquals($message['mutations'][0], $mutation); - return true; - }, - [ - 'commitTimestamp' => \DateTime::createFromFormat('U', (string) time())->format(Timestamp::FORMAT) - ] - ); + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); + $client = new SpannerClient([ + 'projectId' => 'my-project', + 'gapicSpannerClient' => $this->spannerClient->reveal() + ]); $snippet = $this->snippetFromClass(CommitTimestamp::class); $snippet->addLocal('id', $id); diff --git a/Spanner/tests/Snippet/DatabaseTest.php b/Spanner/tests/Snippet/DatabaseTest.php index 18ab6970b552..cbecc6dab3d0 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -18,26 +18,34 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\ApiCore\OperationResponse; +use Google\ApiCore\PagedListResponse; +use Google\ApiCore\Page; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Core\Testing\TestHelpers; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\DropDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlResponse; use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; @@ -45,6 +53,19 @@ use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\V1\CommitRequest; +use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\ReadRequest; +use Google\Cloud\Spanner\V1\RollbackRequest; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\ResultSetStats; +use Google\Cloud\Spanner\V1\Transaction as TransactionProto; +use Google\Protobuf\Timestamp as TimestampProto; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\ListOperationsResponse; +use Google\LongRunning\Operation; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -65,6 +86,9 @@ class DatabaseTest extends SnippetTestCase const BACKUP = 'my-backup'; private $spannerClient; + private $databaseAdminClient; + private $instanceAdminClient; + private $operationResponse; private $serializer; private $database; private $instance; @@ -73,6 +97,14 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + $this->serializer = new Serializer(); + $session = $this->prophesize(Session::class); $session->info() ->willReturn([ @@ -90,22 +122,24 @@ public function setUp(): void ->willReturn(null); $sessionPool->clear()->willReturn(null); - $this->serializer = new Serializer(); $this->instance = new Instance( - $this->requestHandler->reveal(), + $this->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->reveal(), $this->serializer, self::PROJECT, self::INSTANCE ); - $this->database = TestHelpers::stub(Database::class, [ - $this->requestHandler->reveal(), + $this->database = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), $this->serializer, $this->instance, self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['requestHandler', 'serializer', 'operation']); + ); } public function testClass() @@ -142,7 +176,7 @@ public function testState() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetDatabaseResponse(['state' => Database::STATE_READY])); + ->willReturn(new DatabaseProto(['state' => Database::STATE_READY])); $res = $snippet->invoke(); @@ -157,19 +191,25 @@ public function testBackups() $snippet = $this->snippetFromMethod(Database::class, 'backups'); $snippet->addLocal('database', $this->database); + $backup = new BackupProto([ + 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) + ]); + + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListBackupsResponse(['backups' => [$backup]])); + $page->getNextPageToken() + ->willReturn(null); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + $this->databaseAdminClient->listBackups( Argument::type(ListBackupsRequest::class), Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ - 'backups' => [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) - ] - ] - ] - ); + ->willReturn($pagedListResponse->reveal()); $res = $snippet->invoke('backups'); @@ -190,7 +230,7 @@ public function testCreateBackup() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); @@ -217,7 +257,7 @@ public function testExists() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetDatabaseResponse(['statements' => []])); + ->willReturn(new DatabaseProto(['name' => 'foo'])); $res = $snippet->invoke(); @@ -229,8 +269,6 @@ public function testExists() */ public function testInfo() { - $db = ['name' => 'foo']; - $snippet = $this->snippetFromMethod(Database::class, 'info'); $snippet->addLocal('database', $this->database); @@ -239,11 +277,11 @@ public function testInfo() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetDatabaseResponse($db)); + ->willReturn(new DatabaseProto(['name' => 'foo'])); $res = $snippet->invoke('info'); - $this->assertEquals($db, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); } @@ -252,8 +290,6 @@ public function testInfo() */ public function testReload() { - $db = ['name' => 'foo']; - $snippet = $this->snippetFromMethod(Database::class, 'reload'); $snippet->addLocal('database', $this->database); @@ -261,14 +297,12 @@ public function testReload() Argument::type(GetDatabaseRequest::class), Argument::type('array') ) - ->shouldBeCalledOnce() - ->willReturn($db, - 2 - ); + ->shouldBeCalledTimes(2) + ->willReturn(new DatabaseProto(['name' => 'foo'])); $res = $snippet->invoke('info'); - $this->assertEquals($db, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); } @@ -285,7 +319,7 @@ public function testCreate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); @@ -307,7 +341,7 @@ public function testRestore() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); @@ -327,7 +361,7 @@ public function testUpdateDdl() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $snippet->invoke(); @@ -346,7 +380,7 @@ public function testUpdateDdlBatch() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $snippet->invoke(); @@ -398,11 +432,10 @@ public function testDdl() public function testSnapshot() { $this->spannerClient->beginTransaction( - null, - ['id' => self::TRANSACTION] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $snippet = $this->snippetFromMethod(Database::class, 'snapshot'); $snippet->addLocal('database', $this->database); @@ -414,14 +447,13 @@ public function testSnapshot() public function testSnapshotReadTimestamp() { $this->spannerClient->beginTransaction( - null, - [ + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto([ 'id' => self::TRANSACTION, - 'readTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + 'read_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'snapshot', 1); $snippet->addLocal('database', $this->database); @@ -433,30 +465,33 @@ public function testSnapshotReadTimestamp() public function testRunTransaction() { $this->spannerClient->beginTransaction( - null, - ['id' => self::TRANSACTION] - ); + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $this->spannerClient->commit( - null, - ['commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString()] - ); + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $this->spannerClient->executeStreamingSql( - null, - $this->yieldRows([ + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([ [ 'name' => 'loginCount', 'type' => Database::TYPE_INT64, 'value' => 0 ] - ]) - ); + ])); $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); $snippet->addLocal('database', $this->database); @@ -472,36 +507,43 @@ public function testRunTransactionRollback() $this->spannerClient->commit(Argument::cetera())->shouldNotBeCalled(); - $this->mockSendRequest(SpannerClient::class, 'rollback', null, null, 1); + $this->spannerClient->rollback( + Argument::type(RollbackRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); $this->spannerClient->executeStreamingSql( - function ($args) { + Argument::that(function ($request) { $this->assertEquals( - $this->serializer->encodeMessage($args)['transaction']['begin']['readWrite'], - ['readLockMode' => 0] + $this->serializer->encodeMessage($request)['transaction']['begin']['readWrite'], + ['readLockMode' => 0, 'multiplexedSessionPreviousTransactionId' => ''] ); return true; - }, - $this->resultGeneratorData([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'timestamp', - 'type' => [ - 'code' => Database::TYPE_TIMESTAMP + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'timestamp', + 'type' => [ + 'code' => Database::TYPE_TIMESTAMP + ] ] ] + ], + 'transaction' => [ + 'id' => self::TRANSACTION ] - ], - 'transaction' => [ - 'id' => self::TRANSACTION ] ] - ]) - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + )])); $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); @@ -515,11 +557,10 @@ function ($args) { public function testTransaction() { $this->spannerClient->beginTransaction( - null, - ['id' => self::TRANSACTION] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $snippet = $this->snippetFromMethod(Database::class, 'transaction'); $snippet->addLocal('database', $this->database); @@ -530,16 +571,16 @@ public function testTransaction() public function testInsert() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['insert']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insert'); $snippet->addLocal('database', $this->database); @@ -549,17 +590,17 @@ function ($args) { public function testInsertBatch() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['insert']) && isset($message['mutations'][1]['insert']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insertBatch'); $snippet->addLocal('database', $this->database); @@ -569,16 +610,16 @@ function ($args) { public function testUpdate() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['update']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'update'); $snippet->addLocal('database', $this->database); @@ -588,17 +629,17 @@ function ($args) { public function testUpdateBatch() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['update']) && isset($message['mutations'][1]['update']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'updateBatch'); $snippet->addLocal('database', $this->database); @@ -608,16 +649,16 @@ function ($args) { public function testInsertOrUpdate() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['insertOrUpdate']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insertOrUpdate'); $snippet->addLocal('database', $this->database); @@ -627,17 +668,17 @@ function ($args) { public function testInsertOrUpdateBatch() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['insertOrUpdate']) && isset($message['mutations'][1]['insertOrUpdate']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insertOrUpdateBatch'); $snippet->addLocal('database', $this->database); @@ -647,16 +688,16 @@ function ($args) { public function testReplace() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['replace']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'replace'); $snippet->addLocal('database', $this->database); @@ -666,17 +707,17 @@ function ($args) { public function testReplaceBatch() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['replace']) && isset($message['mutations'][1]['replace']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'replaceBatch'); $snippet->addLocal('database', $this->database); @@ -686,16 +727,16 @@ function ($args) { public function testDelete() { $this->spannerClient->commit( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['mutations'][0]['delete']); - }, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() - ] - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'delete'); $snippet->addUse(KeySet::class); @@ -706,11 +747,11 @@ function ($args) { public function testExecute() { $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator() - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $snippet = $this->snippetFromMethod(Database::class, 'execute'); $snippet->addLocal('database', $this->database); @@ -721,14 +762,9 @@ public function testExecute() public function testExecuteWithParameterType() { - $this->spannerClient->executeStreamingSql( - function ($args) { - $message = $this->serializer->encodeMessage($args); - return isset($message['params']) - && isset($message['paramTypes']) - && $message['paramTypes']['timestamp']['code'] === Database::TYPE_TIMESTAMP; - }, - $this->resultGeneratorData([ + $message = $this->serializer->decodeMessage( + new PartialResultSet(), + [ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -741,11 +777,42 @@ function ($args) { ] ] ], - 'values' => [null] - ]) + 'values' => [ + ['nullValue' => 0] + ] + ] ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['params']) + && isset($message['paramTypes']) + && $message['paramTypes']['timestamp']['code'] === Database::TYPE_TIMESTAMP; + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'timestamp', + 'type' => [ + 'code' => Database::TYPE_TIMESTAMP + ] + ] + ] + ] + ], + 'values' => [ + ['nullValue' => 0] + ] + ] + )])); $snippet = $this->snippetFromMethod(Database::class, 'execute', 1); $snippet->addLocal('database', $this->database); @@ -757,35 +824,38 @@ function ($args) { public function testExecuteWithEmptyArray() { $this->spannerClient->executeStreamingSql( - function ($args) { - $message = $this->serializer->encodeMessage($args); + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); return isset($message['params']) && isset($message['paramTypes']) && $message['paramTypes']['emptyArrayOfIntegers']['code'] === Database::TYPE_ARRAY && $message['paramTypes']['emptyArrayOfIntegers']['arrayElementType']['code'] === Database::TYPE_INT64; - }, - $this->resultGeneratorData([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'numbers', - 'type' => [ - 'code' => Database::TYPE_ARRAY, - 'arrayElementType' => [ - 'code' => Database::TYPE_INT64 + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'numbers', + 'type' => [ + 'code' => Database::TYPE_ARRAY, + 'arrayElementType' => [ + 'code' => Database::TYPE_INT64 + ] ] ] ] ] - ] - ], - 'values' => [[]] - ]) - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + ], + 'values' => [[]] + ] + )])); $snippet = $this->snippetFromMethod(Database::class, 'execute', 2); $snippet->addLocal('database', $this->database); @@ -797,12 +867,13 @@ function ($args) { public function testExecuteBeginSnapshot() { $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator(false, self::TRANSACTION) + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Database::class, 'execute', 5); $snippet->addLocal('database', $this->database); @@ -814,12 +885,13 @@ public function testExecuteBeginSnapshot() public function testExecuteBeginTransaction() { $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator(false, self::TRANSACTION) + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Database::class, 'execute', 6); $snippet->addLocal('database', $this->database); @@ -831,16 +903,18 @@ public function testExecuteBeginTransaction() public function testExecutePartitionedUpdate() { $this->spannerClient->beginTransaction( - null, - ['id' => self::TRANSACTION] - ); + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); + $stats = new ResultSetStats(['row_count_lower_bound' => 1]); $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator(true) - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], $stats)); $snippet = $this->snippetFromMethod(Database::class, 'executePartitionedUpdate'); $snippet->addLocal('database', $this->database); @@ -852,11 +926,9 @@ public function testExecutePartitionedUpdate() public function testRead() { $this->spannerClient->streamingRead( - null, - $this->resultGenerator() - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream()); $snippet = $this->snippetFromMethod(Database::class, 'read'); $snippet->addLocal('database', $this->database); @@ -868,11 +940,9 @@ public function testRead() public function testReadWithSnapshot() { $this->spannerClient->streamingRead( - null, - $this->resultGenerator(false, self::TRANSACTION) - ); - - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION)); $snippet = $this->snippetFromMethod(Database::class, 'read', 1); $snippet->addLocal('database', $this->database); @@ -885,12 +955,11 @@ public function testReadWithSnapshot() public function testReadWithTransaction() { $this->spannerClient->streamingRead( - null, - $this->resultGenerator(false, self::TRANSACTION) + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Database::class, 'read', 2); $snippet->addLocal('database', $this->database); @@ -914,6 +983,7 @@ public function testClose() $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); + $this->assertNull($res->returnVal()); } public function testIam() @@ -927,13 +997,13 @@ public function testIam() public function testResumeOperation() { - $snippet = $this->snippetFromMagicMethod(Database::class, 'resumeOperation'); + $snippet = $this->snippetFromMethod(Database::class, 'resumeOperation'); $snippet->addLocal('database', $this->database); $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - $this->assertEquals('foo', $res->returnVal()->name()); + $this->assertEquals('foo', $res->returnVal()->getName()); } public function testLongRunningOperations() @@ -941,13 +1011,23 @@ public function testLongRunningOperations() $snippet = $this->snippetFromMethod(Database::class, 'longRunningOperations'); $snippet->addLocal('database', $this->database); - $this->requestHandler - ->sendRequest( - Argument::any(), - 'listOperations', - Argument::cetera() - ) - ->willReturn([$this->getOperationResponseMock()]); + $operation = new Operation(); + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListOperationsResponse(['operations' => [$operation]])); + $page->getNextPageToken() + ->willReturn(null); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + + $operationsClient = $this->prophesize(OperationsClient::class); + $operationsClient->listOperations(Argument::cetera()) + ->willReturn($pagedListResponse->reveal()); + + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledTimes(2) + ->willReturn($operationsClient->reveal()); $res = $snippet->invoke('operations'); diff --git a/Spanner/tests/Snippet/DateTest.php b/Spanner/tests/Snippet/DateTest.php index 54971ee2b429..b1aad898eb5e 100644 --- a/Spanner/tests/Snippet/DateTest.php +++ b/Spanner/tests/Snippet/DateTest.php @@ -19,6 +19,7 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; diff --git a/Spanner/tests/Snippet/InstanceConfigurationTest.php b/Spanner/tests/Snippet/InstanceConfigurationTest.php index d2095a60b091..291900390d96 100644 --- a/Spanner/tests/Snippet/InstanceConfigurationTest.php +++ b/Spanner/tests/Snippet/InstanceConfigurationTest.php @@ -23,11 +23,14 @@ use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\InstanceConfiguration; -use Prophecy\Argument; +use Google\Cloud\Spanner\Serializer; use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -41,7 +44,8 @@ class InstanceConfigurationTest extends SnippetTestCase const PROJECT = 'my-awesome-project'; const CONFIG = 'regional-europe-west'; - private $spannerClient; + private $instanceAdminClient; + private $operationResponse; private $serializer; private $config; @@ -50,8 +54,13 @@ public function setUp(): void $this->checkAndSkipGrpcTests(); $this->serializer = new Serializer(); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + $this->config = new InstanceConfiguration( - $this->requestHandler->reveal(), + $this->instanceAdminClient->reveal(), $this->serializer, self::PROJECT, self::CONFIG, @@ -81,23 +90,15 @@ public function testCreate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new CreateInstanceConfigResponse($this->getOperationResponseMock(),)); + ->willReturn($this->operationResponse->reveal()); $baseConfig = new InstanceConfiguration( - $this->requestHandler->reveal(), + $this->instanceAdminClient->reveal(), $this->serializer, self::PROJECT, self::CONFIG, [] ); - $this->config->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); - $this->config->___setProperty( - 'serializer', - $this->serializer - ); $snippet->addLocal('baseConfig', $baseConfig); $snippet->addLocal('options', []); $snippet->addLocal('instanceConfig', $this->config); @@ -118,9 +119,6 @@ public function testUpdate() ->shouldBeCalledOnce() ->willReturn($this->prophesize(OperationResponse::class)->reveal()); - $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); - $this->config->___setProperty('serializer', $this->serializer); - $snippet->invoke(); } @@ -129,10 +127,12 @@ public function testDelete() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'delete'); $snippet->addLocal('instanceConfig', $this->config); - $this->mockSendRequest(InstanceAdminClient::class, 'deleteInstanceConfig', null, null); + $this->instanceAdminClient->deleteInstanceConfig( + Argument::type(DeleteInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); - $this->config->___setProperty('serializer', $this->serializer); $snippet->invoke(); } @@ -155,13 +155,19 @@ public function testInfo() 'displayName' => self::CONFIG ]; - $this->mockSendRequest(InstanceAdminClient::class, 'getInstanceConfig', null, $info); - - $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); - $this->config->___setProperty('serializer', $this->serializer); + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig([ + 'name' => $info['name'], + 'display_name' => $info['displayName'] + ])); $res = $snippet->invoke('info'); - $this->assertEquals($info, $res->returnVal()); + $this->assertEquals($info['name'], $res->returnVal()['name']); + $this->assertEquals($info['displayName'], $res->returnVal()['displayName']); } public function testExists() @@ -174,14 +180,10 @@ public function testExists() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ + ->willReturn(new InstanceConfig([ 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG), - 'displayName' => self::CONFIG - ] - ); - - $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); - $this->config->___setProperty('serializer', $this->serializer); + 'display_name' => self::CONFIG + ])); $res = $snippet->invoke(); $this->assertEquals('Configuration exists!', $res->output()); @@ -197,12 +199,18 @@ public function testReload() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'reload'); $snippet->addLocal('configuration', $this->config); - $this->mockSendRequest(InstanceAdminClient::class, 'getInstanceConfig', null, $info); - - $this->config->___setProperty('requestHandler', $this->requestHandler->reveal()); - $this->config->___setProperty('serializer', $this->serializer); + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig([ + 'name' => $info['name'], + 'display_name' => $info['displayName'] + ])); $res = $snippet->invoke('info'); - $this->assertEquals($info, $res->returnVal()); + $this->assertEquals($info['name'], $res->returnVal()['name']); + $this->assertEquals($info['displayName'], $res->returnVal()['displayName']); } } diff --git a/Spanner/tests/Snippet/InstanceTest.php b/Spanner/tests/Snippet/InstanceTest.php index 6f9dd620ecd2..584f42b54409 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -18,6 +18,8 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\ApiCore\OperationResponse; +use Google\ApiCore\PagedListResponse; +use Google\ApiCore\Page; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; @@ -25,17 +27,30 @@ use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesResponse; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsResponse; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\InstanceConfiguration; +use Google\Cloud\Spanner\Serializer; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\Operation; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -55,16 +70,37 @@ class InstanceTest extends SnippetTestCase const OPERATION = 'my-operation'; private $spannerClient; + private $instanceAdminClient; + private $databaseAdminClient; private $serializer; private $instance; + private $operationResponse; + private $page; + private $pagedListResponse; public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->operationResponse = $this->prophesize(OperationResponse::class); + $this->operationResponse->withResultFunction(Argument::type('callable')) + ->willReturn($this->operationResponse->reveal()); + + $this->page = $this->prophesize(Page::class); + $this->page->getNextPageToken() + ->willReturn(null); + $this->pagedListResponse = $this->prophesize(PagedListResponse::class); + $this->pagedListResponse->getPage() + ->willReturn($this->page->reveal()); + $this->serializer = new Serializer(); $this->instance = new Instance( - $this->requestHandler->reveal(), + $this->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->reveal(), $this->serializer, self::PROJECT, self::INSTANCE @@ -100,12 +136,8 @@ public function testCreate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -129,12 +161,8 @@ public function testInfo() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetInstanceResponse(['nodeCount' => 1])); + ->willReturn(new InstanceProto(['node_count' => 1])); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke(); $this->assertEquals('1', $res->output()); } @@ -149,12 +177,8 @@ public function testExists() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetInstanceResponse(['foo' => 'bar'])); - - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); + ->willReturn(new InstanceProto(['name' => 'foo'])); +; $res = $snippet->invoke(); $this->assertEquals('Instance exists!', $res->output()); } @@ -169,12 +193,8 @@ public function testReload() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetInstanceResponse(['nodeCount' => 1])); + ->willReturn(new InstanceProto(['node_count' => 1])); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('info'); $info = $this->instance->info(); $this->assertEquals($info, $res->returnVal()); @@ -191,12 +211,8 @@ public function testState() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn(new GetInstanceResponse(['state' => Instance::STATE_READY])); + ->willReturn(new InstanceProto(['state' => Instance::STATE_READY])); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke(); $this->assertEquals('Instance is ready!', $res->output()); } @@ -211,12 +227,8 @@ public function testUpdate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $snippet->invoke(); } @@ -225,12 +237,12 @@ public function testDelete() $snippet = $this->snippetFromMethod(Instance::class, 'delete'); $snippet->addLocal('instance', $this->instance); - $this->mockSendRequest(InstanceAdminClient::class, 'deleteInstance', null, null); + $this->instanceAdminClient->deleteInstance( + Argument::type(DeleteInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $snippet->invoke(); } @@ -244,12 +256,8 @@ public function testCreateDatabase() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -266,12 +274,8 @@ public function testCreateDatabaseFromBackup() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -291,28 +295,24 @@ public function testDatabases() $snippet = $this->snippetFromMethod(Instance::class, 'databases'); $snippet->addLocal('instance', $this->instance); + $database = new DatabaseProto([ + 'name' => DatabaseAdminClient::databaseName( + self::PROJECT, + self::INSTANCE, + self::DATABASE + ) + ]); + + $this->page->getResponseObject() + ->willReturn(new ListDatabasesResponse(['databases' => [$database]])); + $this->databaseAdminClient->listDatabases( Argument::type(ListDatabasesRequest::class), Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ - 'databases' => [ - [ - 'name' => DatabaseAdminClient::databaseName( - self::PROJECT, - self::INSTANCE, - self::DATABASE - ) - ] - ] - ] - ); + ->willReturn($this->pagedListResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('databases'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -334,28 +334,24 @@ public function testBackups() $snippet = $this->snippetFromMethod(Instance::class, 'backups'); $snippet->addLocal('instance', $this->instance); + $backup = new BackupProto([ + 'name' => DatabaseAdminClient::backupName( + self::PROJECT, + self::INSTANCE, + self::BACKUP + ) + ]); + + $this->page->getResponseObject() + ->willReturn(new ListBackupsResponse(['backups' => [$backup]])); + $this->databaseAdminClient->listBackups( Argument::type(ListBackupsRequest::class), Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ - 'backups' => [ - [ - 'name' => DatabaseAdminClient::backupName( - self::PROJECT, - self::INSTANCE, - self::BACKUP - ) - ] - ] - ] - ); + ->willReturn($this->pagedListResponse->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('backups'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -370,6 +366,10 @@ public function testBackupOperations() self::OPERATION ); + $operation = new Operation(['name' => $backupOperationName]); + $this->page->getResponseObject() + ->willReturn(new ListBackupOperationsResponse(['operations' => [$operation]])); + $snippet = $this->snippetFromMethod(Instance::class, 'backupOperations'); $snippet->addLocal('instance', $this->instance); @@ -378,19 +378,11 @@ public function testBackupOperations() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ - 'operations' => [ - [ - 'name' => $backupOperationName - ] - ] - ] - ); + ->willReturn($this->pagedListResponse->reveal()); + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationsClient::class)->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('backupOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); @@ -405,6 +397,10 @@ public function testDatabaseOperations() self::OPERATION ); + $operation = new Operation(['name' => $databaseOperationName]); + $this->page->getResponseObject() + ->willReturn(new ListDatabaseOperationsResponse(['operations' => [$operation]])); + $snippet = $this->snippetFromMethod(Instance::class, 'databaseOperations'); $snippet->addLocal('instance', $this->instance); @@ -413,19 +409,11 @@ public function testDatabaseOperations() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn([ - 'operations' => [ - [ - 'name' => $databaseOperationName - ] - ] - ] - ); + ->willReturn($this->pagedListResponse->reveal()); + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledOnce() + ->willReturn($this->prophesize(OperationsClient::class)->reveal()); - $this->instance->___setProperty( - 'requestHandler', - $this->requestHandler->reveal() - ); $res = $snippet->invoke('databaseOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); diff --git a/Spanner/tests/Snippet/ResultTest.php b/Spanner/tests/Snippet/ResultTest.php index 4644241572cd..ed667d8e7e37 100644 --- a/Spanner/tests/Snippet/ResultTest.php +++ b/Spanner/tests/Snippet/ResultTest.php @@ -45,7 +45,7 @@ public function setUp(): void $result = $this->prophesize(Result::class); $database = $this->prophesize(Database::class); $result->rows() - ->willReturn($this->resultGenerator()); + ->willReturn($this->resultGeneratorStream()); $result->metadata() ->willReturn([]); $result->columns() @@ -139,7 +139,7 @@ public function testTransaction() $this->assertInstanceOf(Transaction::class, $res->returnVal()); } - private function resultGenerator() + private function resultGeneratorStream() { yield []; } diff --git a/Spanner/tests/Snippet/SnapshotTest.php b/Spanner/tests/Snippet/SnapshotTest.php index 5dd38ddea676..4104d8e3ba50 100644 --- a/Spanner/tests/Snippet/SnapshotTest.php +++ b/Spanner/tests/Snippet/SnapshotTest.php @@ -49,14 +49,14 @@ public function setUp(): void $operation = $this->prophesize(Operation::class); $session = $this->prophesize(Session::class); - $this->snapshot = TestHelpers::stub(Snapshot::class, [ + $this->snapshot = new Snapshot( $operation->reveal(), $session->reveal(), [ 'id' => self::TRANSACTION, 'readTimestamp' => new Timestamp(new \DateTime()) ] - ], ['operation']); + ); } public function testClass() diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index 8e3b6e76cb78..82285099dcc3 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -40,6 +40,7 @@ use Google\Cloud\Spanner\PgOid; use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\Serializer; use Google\Protobuf\Duration; /** diff --git a/Spanner/tests/Snippet/StructTypeTest.php b/Spanner/tests/Snippet/StructTypeTest.php index 2b0391935dfe..1ed99f178f28 100644 --- a/Spanner/tests/Snippet/StructTypeTest.php +++ b/Spanner/tests/Snippet/StructTypeTest.php @@ -73,14 +73,14 @@ public function setUp(): void ->willReturn(null); $this->serializer = new Serializer(); - $this->database = TestHelpers::stub(Database::class, [ + $this->database = new Database( $this->requestHandler->reveal(), $this->serializer, $instance->reveal(), self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation', 'requestHandler', 'serializer']); + ); $this->type = new StructType(); } @@ -127,8 +127,6 @@ function ($args) use ($fields, $values) { 'values' => $values ]) ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromClass(StructType::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); $snippet->addLocal('database', $this->database); diff --git a/Spanner/tests/Snippet/StructValueTest.php b/Spanner/tests/Snippet/StructValueTest.php index 24601f3c8e86..4ec903af8a3e 100644 --- a/Spanner/tests/Snippet/StructValueTest.php +++ b/Spanner/tests/Snippet/StructValueTest.php @@ -72,14 +72,14 @@ public function setUp(): void ->willReturn(null); $this->serializer = new Serializer(); - $this->database = TestHelpers::stub(Database::class, [ + $this->database = new Database( $this->requestHandler->reveal(), $this->serializer, $instance->reveal(), self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation']); + ); $this->value = new StructValue(); } @@ -150,8 +150,6 @@ function ($args) use ($values, $fields) { ]) ); - $this->refreshOperation($this->database, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromClass(StructValue::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); diff --git a/Spanner/tests/Snippet/TransactionTest.php b/Spanner/tests/Snippet/TransactionTest.php index 31b747eae17e..b30836d86a39 100644 --- a/Spanner/tests/Snippet/TransactionTest.php +++ b/Spanner/tests/Snippet/TransactionTest.php @@ -64,11 +64,11 @@ public function setUp(): void $session->name() ->willReturn('database'); - $this->transaction = TestHelpers::stub(Transaction::class, [ + $this->transaction = new Transaction( $operation->reveal(), $session->reveal(), self::TRANSACTION - ], ['operation', 'isRetry']); + ); } public function testClass() @@ -99,12 +99,13 @@ public function testClassReturnTransaction() public function testExecute() { $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator() + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream() ); - $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMagicMethod(Transaction::class, 'execute'); $snippet->addLocal('transaction', $this->transaction); $res = $snippet->invoke('result'); @@ -115,12 +116,13 @@ public function testExecute() public function testExecuteUpdate() { $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator(true) + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGenerator(true) ); - $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate'); $snippet->addLocal('transaction', $this->transaction); $res = $snippet->invoke('modifiedRowCount'); @@ -166,8 +168,6 @@ function ($args) use ($expectedSql, $expectedParams, $expectedStructData) { $this->resultGenerator(true) ); - $this->refreshOperation($this->transaction, $this->requestHandler->reveal(), $this->serializer); - $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate', 1); $snippet->addUse(Database::class); $snippet->addUse(StructType::class); @@ -182,8 +182,9 @@ function ($args) use ($expectedSql, $expectedParams, $expectedStructData) { public function testExecuteUpdateBatch() { $this->spannerClient->executeBatchDml( - null, - [ + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse([ 'resultSets' => [ [ 'stats' => [ @@ -192,7 +193,7 @@ public function testExecuteUpdateBatch() ] ] ] - ); + )); $this->refreshOperation( $this->transaction, @@ -210,15 +211,16 @@ public function testExecuteUpdateBatch() public function testExecuteUpdateBatchError() { $this->spannerClient->executeBatchDml( - null, - [ - 'resultSets' => [], - 'status' => [ + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse([ + 'result_sets' => [], + 'status' => new Status([ 'code' => 3, 'message' => 'foo' - ] + ]) ] - ); + )); $this->refreshOperation( $this->transaction, @@ -236,8 +238,9 @@ public function testExecuteUpdateBatchError() public function testRead() { $this->spannerClient->streamingRead( - null, - $this->resultGenerator() + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream() ); $this->refreshOperation( @@ -441,11 +444,12 @@ public function testRollback() public function testCommit() { $this->spannerClient->commit( - null, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString() + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) ] - ); + )); $this->refreshOperation( $this->transaction, @@ -463,12 +467,12 @@ public function testGetCommitStats() { $expectedCommitStats = new CommitStats(['mutation_count' => 4]); $this->spannerClient->commit( - null, - [ - 'commitTimestamp' => (new Timestamp(new \DateTime()))->formatAsString(), - 'commitStats' => $expectedCommitStats, - ] - ); + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]), + 'commit_stats' => $expectedCommitStats, + ])); $this->refreshOperation( $this->transaction, diff --git a/Spanner/tests/Snippet/TransactionalReadMethodsTest.php b/Spanner/tests/Snippet/TransactionalReadMethodsTest.php index 9943f3c7a613..32768588511c 100644 --- a/Spanner/tests/Snippet/TransactionalReadMethodsTest.php +++ b/Spanner/tests/Snippet/TransactionalReadMethodsTest.php @@ -76,6 +76,7 @@ public function setUp(): void $this->session->name() ->willReturn('sessionName'); $this->operation = $this->prophesize(Operation::class); + $this->spannerClient = $this->prophesize(SpannerClient::class); } public function clientAndSnippetExecute() @@ -96,8 +97,11 @@ public function testExecute($localName, $client, $snippet) $this->checkAndSkipGrpcTests(); $this->spannerClient->executeStreamingSql( - null, - $this->resultGenerator([ + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -169,8 +173,6 @@ function ($args) { ]) ); - $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); - $snippet->addLocal($localName, $client); $res = $snippet->invoke('timestamp'); @@ -229,8 +231,6 @@ function ($args) { ]) ); - $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); - $snippet->addLocal($localName, $client); $res = $snippet->invoke('emptyArray'); @@ -301,8 +301,6 @@ function ($args) use ($values, $fields) { ]) ); - $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); - $snippet->addLocal($localName, $client); $res = $snippet->invoke('fullName'); @@ -422,8 +420,9 @@ public function testRead($localName, $client, $snippet) $this->checkAndSkipGrpcTests(); $this->spannerClient->streamingRead( - null, - $this->resultGenerator([ + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -440,8 +439,6 @@ public function testRead($localName, $client, $snippet) ]) ); - $this->refreshOperation($client, $this->requestHandler->reveal(), $this->serializer); - $snippet->addLocal($localName, $client); $res = $snippet->invoke('result'); @@ -461,39 +458,39 @@ private function setupDatabase() $sessionPool->setDatabase(Argument::any()) ->willReturn(null); - return \Google\Cloud\Core\Testing\TestHelpers::stub(Database::class, [ + return new Database( $this->requestHandler->reveal(), $this->serializer, $instance->reveal(), self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation']); + ); } private function setupTransaction() { $this->setUp(); - return \Google\Cloud\Core\Testing\TestHelpers::stub(Transaction::class, [ + return new Transaction( $this->operation->reveal(), $this->session->reveal(), self::TRANSACTION - ], ['operation']); + ); } private function setupSnapshot() { $this->setUp(); - return \Google\Cloud\Core\Testing\TestHelpers::stub(Snapshot::class, [ + return new Snapshot( $this->operation->reveal(), $this->session->reveal(), [ 'id' => self::TRANSACTION, 'readTimestamp' => new Timestamp(new \DateTime()) ] - ], ['operation']); + ); } private function setupBatch() @@ -509,14 +506,14 @@ private function setupBatch() ) ]); - return \Google\Cloud\Core\Testing\TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->requestHandler->reveal(), $this->serializer, false), + return new BatchSnapshot( + new Operation($this->spannerClient->reveal(), $this->serializer, false), $this->session->reveal(), [ 'id' => self::TRANSACTION, 'readTimestamp' => new Timestamp(\DateTime::createFromFormat('U', (string) time())) ] - ], ['operation', 'session']); + ); } private function resultGenerator(array $data) diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index 0c33ecdaad14..049b968c98fd 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -2318,8 +2318,12 @@ function (Transaction $t) use ($sql) { $prop = new \ReflectionProperty($t, 'state'); $prop->setAccessible(true); $prop->setValue($t, Transaction::STATE_COMMITTED); - }, - ['transactionOptions' => ['excludeTxnFromChangeStreams' => true]] + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn([ +'transactionOptions' => ['excludeTxnFromChangeStreams' => true]] ); } From fc182fb0a572477e2a01ef3d378022a7f7ec83cf Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 18 Nov 2024 19:06:15 -0800 Subject: [PATCH 05/12] WIP --- Spanner/tests/Snippet/InstanceTest.php | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Spanner/tests/Snippet/InstanceTest.php b/Spanner/tests/Snippet/InstanceTest.php index 584f42b54409..f2220fd09554 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -445,15 +445,24 @@ public function testLongRunningOperations() $snippet = $this->snippetFromMethod(Instance::class, 'longRunningOperations'); $snippet->addLocal('instance', $this->instance); - $this->requestHandler - ->sendRequest( - Argument::any(), - 'listOperations', - Argument::cetera() - ) - ->willReturn([$this->getOperationResponseMock()]); + $operation = new Operation(); + $page = $this->prophesize(Page::class); + $page->getResponseObject() + ->willReturn(new ListOperationsResponse(['operations' => [$operation]])); + $page->getNextPageToken() + ->willReturn(null); + $pagedListResponse = $this->prophesize(PagedListResponse::class); + $pagedListResponse->getPage() + ->willReturn($page->reveal()); + + $operationsClient = $this->prophesize(OperationsClient::class); + $operationsClient->listOperations(Argument::cetera()) + ->willReturn($pagedListResponse->reveal()); + + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledTimes(2) + ->willReturn($operationsClient->reveal()); - $this->instance->___setProperty('requestHandler', $this->requestHandler->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); From e143fd711cfc004b48bc4b9dfa7c3e414a534c7a Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Wed, 20 Nov 2024 13:55:32 -0800 Subject: [PATCH 06/12] fix tests --- Core/src/Middleware/ExceptionMiddleware.php | 8 +- Spanner/src/Backup.php | 7 +- Spanner/src/Database.php | 9 +-- Spanner/src/Instance.php | 4 - Spanner/src/InstanceConfiguration.php | 3 - Spanner/src/Operation.php | 2 - Spanner/src/RequestTrait.php | 8 +- Spanner/src/Serializer.php | 76 ++++++++++++++++++- Spanner/src/Session/Session.php | 4 +- Spanner/src/SpannerClient.php | 68 +---------------- Spanner/tests/Snippet/ArrayTypeTest.php | 2 +- Spanner/tests/Snippet/BackupTest.php | 13 +--- .../tests/Snippet/Batch/BatchClientTest.php | 23 +++--- .../tests/Snippet/Batch/BatchSnapshotTest.php | 16 ++-- .../Snippet/Batch/QueryPartitionTest.php | 15 ++-- .../tests/Snippet/Batch/ReadPartitionTest.php | 18 ++--- Spanner/tests/Snippet/BatchDmlResultTest.php | 7 +- Spanner/tests/Snippet/CommitTimestampTest.php | 11 ++- Spanner/tests/Snippet/DatabaseTest.php | 46 ++++------- Spanner/tests/Snippet/DateTest.php | 1 - .../Snippet/InstanceConfigurationTest.php | 5 +- Spanner/tests/Snippet/InstanceTest.php | 28 +++---- Spanner/tests/Snippet/MutationTraitTest.php | 19 +---- Spanner/tests/Snippet/SnapshotTest.php | 3 +- Spanner/tests/Snippet/SpannerClientTest.php | 13 ++-- Spanner/tests/Snippet/StructTypeTest.php | 7 +- Spanner/tests/Snippet/StructValueTest.php | 7 +- Spanner/tests/Snippet/TransactionTest.php | 21 ++--- .../Snippet/TransactionalReadTraitTest.php | 56 +++++++------- Spanner/tests/System/BackupTest.php | 2 +- Spanner/tests/Unit/BackupTest.php | 2 +- Spanner/tests/Unit/Batch/BatchClientTest.php | 2 +- .../tests/Unit/Batch/BatchSnapshotTest.php | 3 +- Spanner/tests/Unit/DatabaseTest.php | 2 +- .../tests/Unit/InstanceConfigurationTest.php | 11 +-- Spanner/tests/Unit/InstanceTest.php | 12 ++- Spanner/tests/Unit/OperationTest.php | 7 +- Spanner/tests/Unit/SpannerClientTest.php | 2 +- Spanner/tests/Unit/TransactionTest.php | 2 +- Spanner/tests/Unit/TransactionTypeTest.php | 8 +- 40 files changed, 251 insertions(+), 302 deletions(-) diff --git a/Core/src/Middleware/ExceptionMiddleware.php b/Core/src/Middleware/ExceptionMiddleware.php index 0afffeb5bc3d..212c76acc2c1 100644 --- a/Core/src/Middleware/ExceptionMiddleware.php +++ b/Core/src/Middleware/ExceptionMiddleware.php @@ -37,6 +37,7 @@ use Google\ApiCore\Call; use Google\ApiCore\ClientStream; use Google\ApiCore\Middleware\MiddlewareInterface; +use Google\ApiCore\Serializer; use Google\ApiCore\ServerStream; use Google\Cloud\Core\RequestProcessorTrait; use GuzzleHttp\Promise\PromiseInterface; @@ -56,8 +57,10 @@ class ExceptionMiddleware implements MiddlewareInterface /** @var callable */ private $nextHandler; - public function __construct(callable $nextHandler) { + public function __construct(callable $nextHandler) + { $this->nextHandler = $nextHandler; + $this->serializer = new Serializer(); } /** @@ -71,7 +74,7 @@ public function __invoke(Call $call, array $options) $response = ($this->nextHandler)($call, $options); if ($response instanceof PromiseInterface) { return $response->then(null, function ($value) { - if ($value instanceof \Google\ApiCore\ApiException) { + if ($value instanceof ApiException) { throw $this->convertToGoogleException($value); } if ($value instanceof Throwable) { @@ -83,4 +86,3 @@ public function __invoke(Call $call, array $options) return $response; } } - diff --git a/Spanner/src/Backup.php b/Spanner/src/Backup.php index 5cbc27b8faed..27dbd8261ffb 100644 --- a/Spanner/src/Backup.php +++ b/Spanner/src/Backup.php @@ -20,12 +20,9 @@ use Closure; use DateTimeInterface; use Google\ApiCore\OperationResponse; -use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\Iterator\PageIterator; use Google\ApiCore\ValidationException; -use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; -use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Backup\State; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; @@ -419,7 +416,7 @@ private function fullyQualifiedBackupName($name): string $instance, $name ); - //@codeCoverageIgnoreStart + //@codeCoverageIgnoreStart } catch (ValidationException $e) { return $name; } diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 3fe0fb3ed557..8e3c89678a11 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -22,15 +22,12 @@ use Google\ApiCore\OperationResponse; use Google\ApiCore\RetrySettings; use Google\ApiCore\ValidationException; -use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\AbortedException; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\Iterator\PageIterator; use Google\Cloud\Core\RequestHandler; -use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Core\Retry; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; @@ -2134,8 +2131,8 @@ public function __destruct() { try { $this->close(); - //@codingStandardsIgnoreStart - //@codeCoverageIgnoreStart + //@codingStandardsIgnoreStart + //@codeCoverageIgnoreStart } catch (\Exception $ex) { } //@codeCoverageIgnoreEnd @@ -2455,7 +2452,7 @@ private function fullyQualifiedDatabaseName($name): string $instance, $name ); - //@codeCoverageIgnoreStart + //@codeCoverageIgnoreStart } catch (ValidationException $e) { return $name; } diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index 407e05cb172d..034b41fee091 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -18,15 +18,11 @@ namespace Google\Cloud\Spanner; use Closure; -use Google\ApiCore\ArrayTrait; use Google\ApiCore\OperationResponse; -use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\Iterator\PageIterator; use Google\Cloud\Core\RequestHandler; -use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; diff --git a/Spanner/src/InstanceConfiguration.php b/Spanner/src/InstanceConfiguration.php index 4420e1ea1756..9f5ba1774d79 100644 --- a/Spanner/src/InstanceConfiguration.php +++ b/Spanner/src/InstanceConfiguration.php @@ -21,8 +21,6 @@ use Google\ApiCore\ApiException; use Google\ApiCore\OperationResponse; use Google\ApiCore\ValidationException; -use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; @@ -31,7 +29,6 @@ use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig\Type; use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; -use Google\LongRunning\ListOperationsRequest; use Google\Rpc\Code; /** diff --git a/Spanner/src/Operation.php b/Spanner/src/Operation.php index 488059eec401..22451bd377c7 100644 --- a/Spanner/src/Operation.php +++ b/Spanner/src/Operation.php @@ -811,7 +811,6 @@ public function partitionRead( 'session' => $session->name(), 'table' => $table, 'columns' => $columns, - // 'keySet' => $this->formatKeySet($this->flattenKeySet($keySet)), 'keySet' => $this->flattenKeySet($keySet), 'partitionOptions' => $this->partitionOptions($data) ]; @@ -910,7 +909,6 @@ private function flattenKeySet(KeySet $keySet): array return $this->arrayFilterRemoveNull($keys); } - private function getDatabaseNameFromSession(Session $session): string { return $session->info()['databaseName']; diff --git a/Spanner/src/RequestTrait.php b/Spanner/src/RequestTrait.php index 1e54838dfda5..e335cf094668 100644 --- a/Spanner/src/RequestTrait.php +++ b/Spanner/src/RequestTrait.php @@ -18,11 +18,11 @@ namespace Google\Cloud\Spanner; use Google\ApiCore\ApiException; -use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Iterator\PageIterator; use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\LongRunning\Operation; use Google\Protobuf\Internal\Message; @@ -114,10 +114,12 @@ function (array $args) use ($call) { 'operations' => iterator_to_array($page->getResponseObject()->getOperations()), 'nextResultToken' => $page->getNextPageToken(), ]; - }, [ + }, + [ 'request' => $request, 'callOptions' => $callOptions - ], [ + ], + [ 'itemsKey' => 'operations', 'resultLimit' => $resultLimit ] diff --git a/Spanner/src/Serializer.php b/Spanner/src/Serializer.php index 4b1ef0dd3f78..f137b29e4266 100644 --- a/Spanner/src/Serializer.php +++ b/Spanner/src/Serializer.php @@ -4,6 +4,10 @@ use Google\ApiCore\Serializer as ApiCoreSerializer; use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Type; +use Google\Protobuf\Internal\RepeatedField; +use Google\Protobuf\Value; /** * @internal @@ -12,6 +16,7 @@ class Serializer extends ApiCoreSerializer { use ApiHelperTrait; + private Serializer $serializer; // Self reference for ApiHelperTrait public function __construct() { @@ -95,5 +100,74 @@ public function __construct() $decodeMessageTypeTransformers, $customEncoders, ); + + $this->serializer = $this; + } + + /** + * Utility method to return "fields data" in the format: + * [ + * "name" => "" + * "type" => [] + * ]. + * + * The type is converted from a string like INT64 to ["code" => 2, "typeAnnotation" => 0] + * conforming with the Google\Cloud\Spanner\V1\TypeCode class. + * + * @param ?RepeatedField $fields The array contain list of fields. + * + * @return array The formatted fields data. + */ + private function getFieldDataFromRepeatedFields(?RepeatedField $fields): array + { + if (is_null($fields)) { + return []; + } + + $fieldsData = []; + foreach ($fields as $key => $field) { + $type = $field->getType(); + $typeData = $this->getTypeData($type); + + $fieldsData[$key] = [ + 'name' => $field->getName(), + 'type' => $typeData + ]; + } + + return $fieldsData; + } + + /** + * Utiltiy method to take in a Google\Cloud\Spanner\V1\Type value and return + * the data as an array. The method takes care of array and struct elements. + * + * @param Type $type The "type" object + * + * @return array The formatted data. + */ + private function getTypeData(Type $type): array + { + $data = [ + 'code' => $type->getCode(), + 'typeAnnotation' => $type->getTypeAnnotation(), + 'protoTypeFqn' => $type->getProtoTypeFqn() + ]; + + // If this is a struct field, then recursisevly call getTypeData + if ($type->hasStructType()) { + $nestedType = $type->getStructType(); + $fields = $nestedType->getFields(); + $data['structType'] = [ + 'fields' => $this->getFieldDataFromRepeatedFields($fields) + ]; + } + // If this is an array field, then recursisevly call getTypeData + if ($type->hasArrayElementType()) { + $nestedType = $type->getArrayElementType(); + $data['arrayElementType'] = $this->getTypeData($nestedType); + } + + return $data; } -} \ No newline at end of file +} diff --git a/Spanner/src/Session/Session.php b/Spanner/src/Session/Session.php index 1985059f644e..f293d8e12c5a 100644 --- a/Spanner/src/Session/Session.php +++ b/Spanner/src/Session/Session.php @@ -17,12 +17,10 @@ namespace Google\Cloud\Spanner\Session; -use Google\ApiCore\ArrayTrait; -use Google\Cloud\Spanner\Serializer; -use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\RequestTrait; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\DeleteSessionRequest; use Google\Cloud\Spanner\V1\GetSessionRequest; diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index 6143f03da4ec..8b3f9655cdb6 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -23,16 +23,11 @@ use Google\ApiCore\OperationResponse; use Google\ApiCore\ValidationException; use Google\Auth\FetchAuthTokenInterface; -use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\ClientTrait; -use Google\Cloud\Core\Exception\GoogleException; use Google\Cloud\Core\EmulatorTrait; +use Google\Cloud\Core\Exception\GoogleException; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Core\Iterator\PageIterator; -use Google\Cloud\Core\Middleware\ExceptionMiddleware; -use Google\Cloud\Core\RequestProcessorTrait; -use Google\Cloud\Core\ValidateTrait; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigOperationsRequest; @@ -40,9 +35,9 @@ use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesRequest; use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; use Google\Cloud\Spanner\Batch\BatchClient; +use Google\Cloud\Spanner\Middleware\SpannerMiddleware; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; -use Google\LongRunning\ListOperationsRequest; use Google\Protobuf\Duration; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\StreamInterface; @@ -252,62 +247,7 @@ public function __construct(array $config = []) ); } $this->projectId = $this->detectProjectId($config); - $this->serializer = new Serializer([], [ - 'google.spanner.v1.KeySet' => function ($v) { - // exit("TEST"); - $keys = $this->pluck('keys', $keySet, false); - if ($keys) { - $keySet['keys'] = array_map( - fn ($key) => $this->formatListForApi((array) $key), - $keys - ); - } - - if (isset($keySet['ranges'])) { - $keySet['ranges'] = array_map(function ($rangeItem) { - return array_map([$this, 'formatListForApi'], $rangeItem); - }, $keySet['ranges']); - - if (empty($keySet['ranges'])) { - unset($keySet['ranges']); - } - } - - return $this->decodeMessage(new KeySet(), $keySet); - }, - ], [], [], [ - // A custom encoder that short-circuits the encodeMessage in Serializer class, - // but only if the argument is of the type PartialResultSet. - PartialResultSet::class => function ($msg) { - $data = json_decode($msg->serializeToJsonString(), true); - - // We only override metadata fields, if it actually exists in the response. - // This is specially important for large data sets which is received in chunks. - // Metadata is only received in the first 'chunk' and we don't want to set empty metadata fields - // when metadata was not returned from the server. - if (isset($data['metadata'])) { - // The transaction id is serialized as a base64 encoded string in $data. So, we - // add a step to get the transaction id using a getter instead of the serialized value. - // The null-safe operator is used to handle edge cases where the relevant fields are not present. - $data['metadata']['transaction']['id'] = (string) $msg?->getMetadata()?->getTransaction()?->getId(); - - // Helps convert metadata enum values from string types to their respective code/annotation - // pairs. Ex: INT64 is converted to {code: 2, typeAnnotation: 0}. - $fields = $msg->getMetadata()?->getRowType()?->getFields(); - $data['metadata']['rowType']['fields'] = $this->getFieldDataFromRepeatedFields($fields); - } - - // These fields in stats should be an int - if (isset($data['stats']['rowCountLowerBound'])) { - $data['stats']['rowCountLowerBound'] = (int) $data['stats']['rowCountLowerBound']; - } - if (isset($data['stats']['rowCountExact'])) { - $data['stats']['rowCountExact'] = (int) $data['stats']['rowCountExact']; - } - - return $data; - } - ]); + $this->serializer = new Serializer(); // Adds some defaults // gccl needs to be present for handwritten clients @@ -316,7 +256,7 @@ public function __construct(array $config = []) 'serializer' => $this->serializer, ]; $middleware = function (MiddlewareInterface $handler) { - return new ExceptionMiddleware($handler); + return new SpannerMiddleware($handler); }; $this->spannerClient = $config['gapicSpannerClient'] ?? new GapicSpannerClient($clientConfig); $this->spannerClient->addMiddleware($middleware); diff --git a/Spanner/tests/Snippet/ArrayTypeTest.php b/Spanner/tests/Snippet/ArrayTypeTest.php index 3f36806155b1..a250fcebff01 100644 --- a/Spanner/tests/Snippet/ArrayTypeTest.php +++ b/Spanner/tests/Snippet/ArrayTypeTest.php @@ -18,7 +18,6 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; @@ -26,6 +25,7 @@ use Google\Cloud\Spanner\ArrayType; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructType; diff --git a/Spanner/tests/Snippet/BackupTest.php b/Spanner/tests/Snippet/BackupTest.php index 52fbae8d5b93..75ddc2077bc3 100644 --- a/Spanner/tests/Snippet/BackupTest.php +++ b/Spanner/tests/Snippet/BackupTest.php @@ -23,19 +23,18 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\DeleteBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest; -use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; -use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; use Google\Cloud\Spanner\Admin\Database\V1\UpdateBackupRequest; -use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\SpannerClient; use Google\LongRunning\Client\OperationsClient; use Google\LongRunning\ListOperationsRequest; @@ -147,7 +146,6 @@ public function testCreateCopy() ->shouldBeCalledOnce() ->willReturn($this->operationResponse->reveal()); - $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -163,7 +161,6 @@ public function testDelete() ) ->shouldBeCalledOnce(); - $snippet->invoke(); } @@ -197,7 +194,6 @@ public function testInfo() ->shouldBeCalledOnce() ->willReturn(new BackupProto($backup)); - $res = $snippet->invoke('info'); $this->assertEquals($backup['name'], $res->returnVal()['name']); $snippet->invoke(); @@ -229,7 +225,6 @@ public function testReload() ->shouldBeCalledTimes(2) ->willReturn(new BackupProto($backup)); - $res = $snippet->invoke('info'); $this->assertEquals($backup['name'], $res->returnVal()['name']); $snippet->invoke(); @@ -247,7 +242,6 @@ public function testState() ->shouldBeCalledOnce() ->willReturn(new BackupProto(['state' => Backup::STATE_READY])); - $res = $snippet->invoke(); $this->assertEquals('Backup is ready!', $res->output()); } @@ -269,7 +263,6 @@ public function testUpdateExpireTime() ->shouldBeCalledOnce() ->willReturn(new BackupProto($backup)); - $res = $snippet->invoke('info'); $this->assertEquals($backup['name'], $res->returnVal()['name']); $this->assertEquals( diff --git a/Spanner/tests/Snippet/Batch/BatchClientTest.php b/Spanner/tests/Snippet/Batch/BatchClientTest.php index c08cf9096dd0..4be07663d1ff 100644 --- a/Spanner/tests/Snippet/Batch/BatchClientTest.php +++ b/Spanner/tests/Snippet/Batch/BatchClientTest.php @@ -17,35 +17,33 @@ namespace Google\Cloud\Spanner\Tests\Snippet\Batch; -use Google\Cloud\Spanner\Serializer; -use Google\Cloud\Core\RequestHandler; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\PubSub\PubSubClient; -use Google\Cloud\PubSub\Topic; use Google\Cloud\PubSub\Message; +use Google\Cloud\PubSub\PubSubClient; use Google\Cloud\PubSub\Subscription; +use Google\Cloud\PubSub\Topic; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Operation; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\CreateSessionRequest; -use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Partition; use Google\Cloud\Spanner\V1\PartitionQueryRequest; use Google\Cloud\Spanner\V1\PartitionResponse; -use Google\Cloud\Spanner\V1\Partition; -use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; use Google\Protobuf\Timestamp as TimestampProto; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner @@ -148,8 +146,7 @@ public function testPubSubExample() new Partition(['partition_token' => $partition1->token()]), new Partition(['partition_token' => $partition2->token()]), ] - ]) - ); + ])); $this->spannerClient->executeStreamingSql( Argument::that(function ($request) use ($partition1) { diff --git a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php index 7f1e773981b3..cfcb3e331a31 100644 --- a/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php +++ b/Spanner/tests/Snippet/Batch/BatchSnapshotTest.php @@ -19,8 +19,6 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\PartitionInterface; @@ -30,21 +28,22 @@ use Google\Cloud\Spanner\Result; use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\CreateSessionRequest; -use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\ExecuteSqlRequest; -use Google\Cloud\Spanner\V1\PartitionReadRequest; +use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Partition; use Google\Cloud\Spanner\V1\PartitionQueryRequest; +use Google\Cloud\Spanner\V1\PartitionReadRequest; use Google\Cloud\Spanner\V1\PartitionResponse; -use Google\Cloud\Spanner\V1\Partition; -use Google\Cloud\Spanner\V1\PartialResultSet; +use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\Transaction; use Google\Protobuf\Timestamp as TimestampProto; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner @@ -145,7 +144,6 @@ public function testClose() $this->session->delete([]) ->shouldBeCalled(); - $snippet = $this->snippetFromMethod(BatchSnapshot::class, 'close'); $snippet->addLocal('snapshot', $this->snapshot); diff --git a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php index bb6db70a170c..a323023c1544 100644 --- a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php @@ -19,23 +19,21 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\QueryPartition; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\CreateSessionRequest; -use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Partition; use Google\Cloud\Spanner\V1\PartitionQueryRequest; use Google\Cloud\Spanner\V1\PartitionResponse; -use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\Transaction; use Google\Protobuf\Timestamp as TimestampProto; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner @@ -81,8 +79,7 @@ public function testClass() ->willReturn(new Transaction([ 'id' => self::TRANSACTION, 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) - ]) - ); + ])); $this->spannerClient->partitionQuery( Argument::type(PartitionQueryRequest::class), Argument::type('array') diff --git a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php index df076c4e51c7..fb0f0707fa70 100644 --- a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php @@ -19,24 +19,22 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Batch\ReadPartition; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Timestamp; +use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\CreateSessionRequest; -use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Partition; use Google\Cloud\Spanner\V1\PartitionReadRequest; use Google\Cloud\Spanner\V1\PartitionResponse; -use Google\Cloud\Spanner\V1\Partition; +use Google\Cloud\Spanner\V1\Session as SessionProto; use Google\Cloud\Spanner\V1\Transaction; use Google\Protobuf\Timestamp as TimestampProto; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner @@ -89,8 +87,7 @@ public function testClass() ->willReturn(new Transaction([ 'id' => self::TRANSACTION, 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) - ]) - ); + ])); $this->spannerClient->partitionRead( Argument::type(PartitionReadRequest::class), Argument::type('array') @@ -98,8 +95,7 @@ public function testClass() 'partitions' => [ new Partition(['partition_token' => 'foo']) ] - ] - )); + ])); $client = new BatchClient( new Operation($this->spannerClient->reveal(), $this->serializer, false), diff --git a/Spanner/tests/Snippet/BatchDmlResultTest.php b/Spanner/tests/Snippet/BatchDmlResultTest.php index 24df03f8e9f9..f1905d220dcb 100644 --- a/Spanner/tests/Snippet/BatchDmlResultTest.php +++ b/Spanner/tests/Snippet/BatchDmlResultTest.php @@ -19,22 +19,21 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Core\TimeTrait; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\BatchDmlResult; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; -use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\V1\BeginTransactionRequest; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CommitRequest; use Google\Cloud\Spanner\V1\CommitResponse; -use Google\Cloud\Spanner\V1\Transaction as TransactionProto; use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; +use Google\Cloud\Spanner\V1\Transaction as TransactionProto; use Google\Protobuf\Timestamp as TimestampProto; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Snippet/CommitTimestampTest.php b/Spanner/tests/Snippet/CommitTimestampTest.php index 9fc76a206055..2d2bfa554d0c 100644 --- a/Spanner/tests/Snippet/CommitTimestampTest.php +++ b/Spanner/tests/Snippet/CommitTimestampTest.php @@ -19,19 +19,18 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Timestamp; use Google\Cloud\Spanner\CommitTimestamp; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; -use Google\Cloud\Spanner\V1\CreateSessionRequest; -use Google\Cloud\Spanner\V1\Session; -use Google\Cloud\Spanner\V1\DeleteSessionRequest; use Google\Cloud\Spanner\V1\CommitRequest; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\V1\CommitResponse; +use Google\Cloud\Spanner\V1\CreateSessionRequest; +use Google\Cloud\Spanner\V1\DeleteSessionRequest; +use Google\Cloud\Spanner\V1\Session; use Google\Protobuf\Timestamp as TimestampProto; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner diff --git a/Spanner/tests/Snippet/DatabaseTest.php b/Spanner/tests/Snippet/DatabaseTest.php index cbecc6dab3d0..cb96afa60032 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -18,28 +18,26 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\ApiCore\OperationResponse; -use Google\ApiCore\PagedListResponse; use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; use Google\Cloud\Spanner\Admin\Database\V1\DropDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlRequest; use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlResponse; use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest; -use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; -use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; -use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; -use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; -use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; -use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; +use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; @@ -52,20 +50,20 @@ use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; +use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CommitRequest; use Google\Cloud\Spanner\V1\CommitResponse; use Google\Cloud\Spanner\V1\ExecuteSqlRequest; use Google\Cloud\Spanner\V1\PartialResultSet; use Google\Cloud\Spanner\V1\ReadRequest; -use Google\Cloud\Spanner\V1\RollbackRequest; -use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\ResultSetStats; +use Google\Cloud\Spanner\V1\RollbackRequest; use Google\Cloud\Spanner\V1\Transaction as TransactionProto; -use Google\Protobuf\Timestamp as TimestampProto; use Google\LongRunning\Client\OperationsClient; use Google\LongRunning\ListOperationsResponse; use Google\LongRunning\Operation; +use Google\Protobuf\Timestamp as TimestampProto; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -178,7 +176,6 @@ public function testState() ->shouldBeCalledOnce() ->willReturn(new DatabaseProto(['state' => Database::STATE_READY])); - $res = $snippet->invoke(); $this->assertEquals('Database is ready!', $res->output()); } @@ -259,7 +256,6 @@ public function testExists() ->shouldBeCalledOnce() ->willReturn(new DatabaseProto(['name' => 'foo'])); - $res = $snippet->invoke(); $this->assertEquals('Database exists!', $res->output()); } @@ -279,7 +275,6 @@ public function testInfo() ->shouldBeCalledOnce() ->willReturn(new DatabaseProto(['name' => 'foo'])); - $res = $snippet->invoke('info'); $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); @@ -300,7 +295,6 @@ public function testReload() ->shouldBeCalledTimes(2) ->willReturn(new DatabaseProto(['name' => 'foo'])); - $res = $snippet->invoke('info'); $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); @@ -321,7 +315,6 @@ public function testCreate() ->shouldBeCalledOnce() ->willReturn($this->operationResponse->reveal()); - $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -343,7 +336,6 @@ public function testRestore() ->shouldBeCalledOnce() ->willReturn($this->operationResponse->reveal()); - $res = $snippet->invoke('operation'); $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } @@ -363,7 +355,6 @@ public function testUpdateDdl() ->shouldBeCalledOnce() ->willReturn($this->operationResponse->reveal()); - $snippet->invoke(); } @@ -382,7 +373,6 @@ public function testUpdateDdlBatch() ->shouldBeCalledOnce() ->willReturn($this->operationResponse->reveal()); - $snippet->invoke(); } @@ -400,7 +390,6 @@ public function testDrop() ) ->shouldBeCalledOnce(); - $snippet->invoke(); } @@ -424,7 +413,6 @@ public function testDdl() ->shouldBeCalledOnce() ->willReturn(new GetDatabaseDdlResponse(['statements' => $stmts])); - $res = $snippet->invoke('statements'); $this->assertEquals($stmts, $res->returnVal()); } @@ -853,7 +841,9 @@ public function testExecuteWithEmptyArray() ] ] ], - 'values' => [[]] + 'values' => [ + ['listValue' => []] + ] ] )])); @@ -871,8 +861,7 @@ public function testExecuteBeginSnapshot() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) - ); + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION)); $snippet = $this->snippetFromMethod(Database::class, 'execute', 5); $snippet->addLocal('database', $this->database); @@ -889,8 +878,7 @@ public function testExecuteBeginTransaction() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) - ); + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION)); $snippet = $this->snippetFromMethod(Database::class, 'execute', 6); $snippet->addLocal('database', $this->database); @@ -957,8 +945,7 @@ public function testReadWithTransaction() $this->spannerClient->streamingRead( Argument::type(ReadRequest::class), Argument::type('array') - )->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) - ); + )->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION)); $snippet = $this->snippetFromMethod(Database::class, 'read', 2); $snippet->addLocal('database', $this->database); @@ -1029,7 +1016,6 @@ public function testLongRunningOperations() ->shouldBeCalledTimes(2) ->willReturn($operationsClient->reveal()); - $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); diff --git a/Spanner/tests/Snippet/DateTest.php b/Spanner/tests/Snippet/DateTest.php index b1aad898eb5e..54971ee2b429 100644 --- a/Spanner/tests/Snippet/DateTest.php +++ b/Spanner/tests/Snippet/DateTest.php @@ -19,7 +19,6 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Date; diff --git a/Spanner/tests/Snippet/InstanceConfigurationTest.php b/Spanner/tests/Snippet/InstanceConfigurationTest.php index 291900390d96..a374be400720 100644 --- a/Spanner/tests/Snippet/InstanceConfigurationTest.php +++ b/Spanner/tests/Snippet/InstanceConfigurationTest.php @@ -20,17 +20,16 @@ use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; -use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\InstanceConfiguration; use Google\Cloud\Spanner\Serializer; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner diff --git a/Spanner/tests/Snippet/InstanceTest.php b/Spanner/tests/Snippet/InstanceTest.php index eef9a4baf635..63ca597a02aa 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -18,40 +18,40 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\ApiCore\OperationResponse; -use Google\ApiCore\PagedListResponse; use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; +use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; -use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; -use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; -use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesResponse; -use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; -use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsResponse; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListBackupsResponse; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsRequest; use Google\Cloud\Spanner\Admin\Database\V1\ListDatabaseOperationsResponse; -use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; -use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; -use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesRequest; +use Google\Cloud\Spanner\Admin\Database\V1\ListDatabasesResponse; +use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; -use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceRequest; -use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceRequest; use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; -use Google\Cloud\Spanner\V1\Client\SpannerClient; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceRequest; use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\InstanceConfiguration; use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\LongRunning\Client\OperationsClient; -use Google\LongRunning\Operation; use Google\LongRunning\ListOperationsResponse; +use Google\LongRunning\Operation; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -179,7 +179,7 @@ public function testExists() ) ->shouldBeCalledOnce() ->willReturn(new InstanceProto(['name' => 'foo'])); -; + $res = $snippet->invoke(); $this->assertEquals('Instance exists!', $res->output()); } diff --git a/Spanner/tests/Snippet/MutationTraitTest.php b/Spanner/tests/Snippet/MutationTraitTest.php index c1291d8c2697..ef03e627c40a 100644 --- a/Spanner/tests/Snippet/MutationTraitTest.php +++ b/Spanner/tests/Snippet/MutationTraitTest.php @@ -19,29 +19,14 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\KeySet; +use Google\Cloud\Spanner\MutationTrait; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; -use Google\Cloud\Spanner\StructType; -use Google\Cloud\Spanner\StructValue; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\Serializer; -use Google\Cloud\Spanner\MutationTrait; use Google\Cloud\Spanner\V1\Client\SpannerClient; -use Google\Cloud\Spanner\V1\CommitResponse\CommitStats; -use Google\Cloud\Spanner\V1\ExecuteSqlRequest; -use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; -use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; -use Google\Cloud\Spanner\V1\ResultSetStats; -use Google\Cloud\Spanner\V1\ResultSet; -use Google\Cloud\Spanner\V1\ReadRequest; -use Google\Rpc\Status; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; /** diff --git a/Spanner/tests/Snippet/SnapshotTest.php b/Spanner/tests/Snippet/SnapshotTest.php index daff1424f047..085846988d97 100644 --- a/Spanner/tests/Snippet/SnapshotTest.php +++ b/Spanner/tests/Snippet/SnapshotTest.php @@ -19,11 +19,10 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Operation; -use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Snapshot; use Google\Cloud\Spanner\Timestamp; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index fb250340bea3..454c377427cb 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -18,20 +18,20 @@ namespace Google\Cloud\Spanner\Tests\Snippet; use Google\ApiCore\OperationResponse; -use Google\ApiCore\PagedListResponse; use Google\ApiCore\Page; +use Google\ApiCore\PagedListResponse; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; +use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; +use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsRequest; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsResponse; -use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesRequest; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesResponse; -use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; -use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Bytes; use Google\Cloud\Spanner\CommitTimestamp; @@ -45,13 +45,12 @@ use Google\Cloud\Spanner\PgJsonb; use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\PgOid; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Timestamp; -use Google\Cloud\Spanner\Serializer; use Google\Protobuf\Duration; -use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Argument; - +use Prophecy\PhpUnit\ProphecyTrait; /** * @group spanner diff --git a/Spanner/tests/Snippet/StructTypeTest.php b/Spanner/tests/Snippet/StructTypeTest.php index d8619ae94188..be6711b21894 100644 --- a/Spanner/tests/Snippet/StructTypeTest.php +++ b/Spanner/tests/Snippet/StructTypeTest.php @@ -19,19 +19,18 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\ArrayType; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructType; -use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\PartialResultSet; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Snippet/StructValueTest.php b/Spanner/tests/Snippet/StructValueTest.php index 6a41943d6f52..539ae8de1671 100644 --- a/Spanner/tests/Snippet/StructValueTest.php +++ b/Spanner/tests/Snippet/StructValueTest.php @@ -19,18 +19,17 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; -use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\StructValue; -use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\PartialResultSet; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Snippet/TransactionTest.php b/Spanner/tests/Snippet/TransactionTest.php index ce16f3b5b885..a4308a7e8057 100644 --- a/Spanner/tests/Snippet/TransactionTest.php +++ b/Spanner/tests/Snippet/TransactionTest.php @@ -19,28 +19,25 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Core\Testing\TestHelpers; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\StructType; use Google\Cloud\Spanner\StructValue; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; -use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CommitRequest; use Google\Cloud\Spanner\V1\CommitResponse; use Google\Cloud\Spanner\V1\CommitResponse\CommitStats; -use Google\Cloud\Spanner\V1\ExecuteSqlRequest; use Google\Cloud\Spanner\V1\ExecuteBatchDmlRequest; use Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse; -use Google\Cloud\Spanner\V1\ResultSetStats; -use Google\Cloud\Spanner\V1\ResultSet; +use Google\Cloud\Spanner\V1\ExecuteSqlRequest; use Google\Cloud\Spanner\V1\ReadRequest; +use Google\Cloud\Spanner\V1\ResultSet; +use Google\Cloud\Spanner\V1\ResultSetStats; use Google\Cloud\Spanner\V1\RollbackRequest; use Google\Protobuf\Timestamp as TimestampProto; use Google\Rpc\Status; @@ -188,8 +185,8 @@ public function testExecuteUpdateWithStruct() ->shouldBeCalledOnce() ->willReturn($this->resultGeneratorStream( [], - new ResultSetStats(['row_count_exact' => 1])) - ); + new ResultSetStats(['row_count_exact' => 1]) + )); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate', 1); $snippet->addUse(Database::class); @@ -235,8 +232,7 @@ public function testExecuteUpdateBatchError() 'code' => 3, 'message' => 'foo' ]) - ] - )); + ])); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdateBatch'); $snippet->addLocal('transaction', $this->transaction); @@ -250,8 +246,7 @@ public function testRead() $this->spannerClient->streamingRead( Argument::type(ReadRequest::class), Argument::type('array') - )->willReturn($this->resultGeneratorStream() - ); + )->willReturn($this->resultGeneratorStream()); $snippet = $this->snippetFromMagicMethod(Transaction::class, 'read'); $snippet->addLocal('transaction', $this->transaction); diff --git a/Spanner/tests/Snippet/TransactionalReadTraitTest.php b/Spanner/tests/Snippet/TransactionalReadTraitTest.php index a4f68807b311..f0ccdbba74f2 100644 --- a/Spanner/tests/Snippet/TransactionalReadTraitTest.php +++ b/Spanner/tests/Snippet/TransactionalReadTraitTest.php @@ -19,24 +19,24 @@ use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; -use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; -use Google\Cloud\Spanner\Serializer; +use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\Transaction; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\ExecuteSqlRequest; -use Google\Cloud\Spanner\V1\ReadRequest; use Google\Cloud\Spanner\V1\PartialResultSet; -use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; +use Google\Cloud\Spanner\V1\ReadRequest; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -211,6 +211,30 @@ public function testExecuteWithEmptyArray($localName, $class) $snippet = $this->snippetFromMethod($class, 'execute', 2); $client = $this->createClientForClass($class); + $partialResultSet = $this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'numbers', + 'type' => [ + 'code' => Database::TYPE_ARRAY, + 'arrayElementType' => [ + 'code' => Database::TYPE_INT64 + ] + ] + ] + ] + ] + ], + 'values' => [ + ['listValue' => []] + ] + ] + ); + $this->spannerClient->executeStreamingSql( Argument::that(function ($request) { $message = $this->serializer->encodeMessage($request); @@ -229,27 +253,7 @@ public function testExecuteWithEmptyArray($localName, $class) Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->resultGeneratorStream([$this->serializer->decodeMessage( - new PartialResultSet(), - [ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'numbers', - 'type' => [ - 'code' => Database::TYPE_ARRAY, - 'arrayElementType' => [ - 'code' => Database::TYPE_INT64 - ] - ] - ] - ] - ] - ], - 'values' => [[]] - ] - )])); + ->willReturn($this->resultGeneratorStream([$partialResultSet])); $snippet->addLocal($localName, $client); @@ -581,7 +585,7 @@ private function createBatchSnapshot() private function createClientForClass($class) { - return match($class) { + return match ($class) { Database::class => $this->createDatabase(), Transaction::class => $this->createTransaction(), Snapshot::class => $this->createSnapshot(), diff --git a/Spanner/tests/System/BackupTest.php b/Spanner/tests/System/BackupTest.php index 5e399173c9fe..b590dd5d8382 100644 --- a/Spanner/tests/System/BackupTest.php +++ b/Spanner/tests/System/BackupTest.php @@ -20,8 +20,8 @@ use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Exception\BadRequestException; use Google\Cloud\Core\Exception\ConflictException; -use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupEncryptionConfig; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; +use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupEncryptionConfig; use Google\Cloud\Spanner\Admin\Database\V1\EncryptionInfo\Type; use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig; use Google\Cloud\Spanner\Backup; diff --git a/Spanner/tests/Unit/BackupTest.php b/Spanner/tests/Unit/BackupTest.php index 71f2cad572af..20beb3320b6b 100644 --- a/Spanner/tests/Unit/BackupTest.php +++ b/Spanner/tests/Unit/BackupTest.php @@ -21,7 +21,6 @@ use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Google\ApiCore\OperationResponse; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; @@ -33,6 +32,7 @@ use Google\Cloud\Spanner\Backup; use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; +use Google\Cloud\Spanner\Serializer; use Google\Protobuf\FieldMask; use Google\Protobuf\Timestamp; use PHPUnit\Framework\TestCase; diff --git a/Spanner/tests/Unit/Batch/BatchClientTest.php b/Spanner/tests/Unit/Batch/BatchClientTest.php index ae6d56fc11d4..662aea1a7f32 100644 --- a/Spanner/tests/Unit/Batch/BatchClientTest.php +++ b/Spanner/tests/Unit/Batch/BatchClientTest.php @@ -18,7 +18,6 @@ namespace Google\Cloud\Spanner\Tests\Unit\Batch; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Timestamp; use Google\Cloud\Core\TimeTrait; use Google\Cloud\Spanner\Batch\BatchClient; @@ -27,6 +26,7 @@ use Google\Cloud\Spanner\Batch\ReadPartition; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\V1\BeginTransactionRequest; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; use Google\Cloud\Spanner\V1\CreateSessionRequest; diff --git a/Spanner/tests/Unit/Batch/BatchSnapshotTest.php b/Spanner/tests/Unit/Batch/BatchSnapshotTest.php index 11c13acd2507..794038e5e408 100644 --- a/Spanner/tests/Unit/Batch/BatchSnapshotTest.php +++ b/Spanner/tests/Unit/Batch/BatchSnapshotTest.php @@ -18,7 +18,6 @@ namespace Google\Cloud\Spanner\Tests\Unit\Batch; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Batch\BatchSnapshot; use Google\Cloud\Spanner\Batch\PartitionInterface; use Google\Cloud\Spanner\Batch\QueryPartition; @@ -26,6 +25,7 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; @@ -36,7 +36,6 @@ use Google\Cloud\Spanner\V1\PartitionReadRequest; use Google\Cloud\Spanner\V1\PartitionResponse; use Google\Cloud\Spanner\V1\ReadRequest; -use Google\Cloud\Spanner\V1\KeySet as KeySetProto; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index 0c33ecdaad14..35d355a3fd07 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -28,7 +28,6 @@ use Google\Cloud\Core\Exception\ServiceException; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\Fixtures; use Google\Cloud\Spanner\Admin\Database\V1\Backup; @@ -44,6 +43,7 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; diff --git a/Spanner/tests/Unit/InstanceConfigurationTest.php b/Spanner/tests/Unit/InstanceConfigurationTest.php index 854b143442d0..32c2d66d5d5a 100644 --- a/Spanner/tests/Unit/InstanceConfigurationTest.php +++ b/Spanner/tests/Unit/InstanceConfigurationTest.php @@ -19,7 +19,6 @@ use Google\ApiCore\ApiException; use Google\ApiCore\OperationResponse; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; @@ -27,6 +26,7 @@ use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\InstanceConfiguration; +use Google\Cloud\Spanner\Serializer; use Google\LongRunning\Operation; use Google\Protobuf\Any; use Google\Rpc\Code; @@ -186,15 +186,12 @@ public function testUpdate() 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, 'foo'), 'display_name' => 'bar2' ]); - $any = $this->prophesize(Any::class); - $any->getValue()->willReturn($expectedInstanceConfig->serializeToString()); - $operation = $this->prophesize(Operation::class); - $operation->getResponse()->willReturn($any->reveal()); - $operation->getDone()->willReturn(true); + $any = new Any(['value' => $expectedInstanceConfig->serializeToString()]); + $operationProto = new Operation(['response' => $any, 'done' => true]); $operationClient = $this->prophesize(\Google\LongRunning\Client\OperationsClient::class); $operationResponse = new OperationResponse('operation-name', $operationClient->reveal(), [ 'operationReturnType' => InstanceConfig::class, - 'lastProtoResponse' => $operation->reveal(), + 'lastProtoResponse' => $operationProto, ]); $this->instanceAdminClient->updateInstanceConfig( diff --git a/Spanner/tests/Unit/InstanceTest.php b/Spanner/tests/Unit/InstanceTest.php index 41ee2ed4d730..b4863381d668 100644 --- a/Spanner/tests/Unit/InstanceTest.php +++ b/Spanner/tests/Unit/InstanceTest.php @@ -20,7 +20,6 @@ use Google\ApiCore\OperationResponse; use Google\ApiCore\Page; use Google\ApiCore\PagedListResponse; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; @@ -39,6 +38,7 @@ use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\CreateSessionRequest; @@ -486,8 +486,14 @@ public function testDatabases() $this->assertEquals('database2', DatabaseAdminClient::parseName($dbs[1]->name())['database']); // Make sure the database->info is prefilled. - $this->assertEquals($databases[0]->__debugInfo(), array_filter($dbs[0]->info())); - $this->assertEquals($databases[1]->__debugInfo(), array_filter($dbs[1]->info())); + $this->assertEquals( + json_decode($databases[0]->serializeToJsonString(), true), + array_filter($dbs[0]->info()) + ); + $this->assertEquals( + json_decode($databases[1]->serializeToJsonString(), true), + array_filter($dbs[1]->info()) + ); } public function testDatabasesPaged() diff --git a/Spanner/tests/Unit/OperationTest.php b/Spanner/tests/Unit/OperationTest.php index ec7d0d861131..295d57d6c9b1 100644 --- a/Spanner/tests/Unit/OperationTest.php +++ b/Spanner/tests/Unit/OperationTest.php @@ -19,7 +19,6 @@ use Google\ApiCore\ServerStream; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Batch\ReadPartition; @@ -28,6 +27,7 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; @@ -506,7 +506,10 @@ public function testPartitionQuery() Argument::that(function ($request) use ($sql, $transactionId, $partitionToken1, $partitionToken2) { $this->assertEquals($request->getSql(), $sql); $this->assertEquals(self::SESSION, $request->getSession()); - $this->assertEquals(['id' => '10'], $request->getParams()->__debugInfo()); + $this->assertEquals( + ['id' => '10'], + json_decode($request->getParams()->serializeToJsonString(), true) + ); $this->assertEquals(Database::TYPE_INT64, $request->getParamTypes()['id']->getCode()); $this->assertEquals($transactionId, $request->getTransaction()->getId()); return true; diff --git a/Spanner/tests/Unit/SpannerClientTest.php b/Spanner/tests/Unit/SpannerClientTest.php index 1abfdfafd0b8..06aaa1073696 100644 --- a/Spanner/tests/Unit/SpannerClientTest.php +++ b/Spanner/tests/Unit/SpannerClientTest.php @@ -20,7 +20,6 @@ use Google\ApiCore\OperationResponse; use Google\ApiCore\Page; use Google\ApiCore\PagedListResponse; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; @@ -44,6 +43,7 @@ use Google\Cloud\Spanner\PgJsonb; use Google\Cloud\Spanner\PgNumeric; use Google\Cloud\Spanner\PgOid; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\SpannerClient; use Google\Cloud\Spanner\Timestamp; use Google\Cloud\Spanner\V1\Client\SpannerClient as GapicSpannerClient; diff --git a/Spanner/tests/Unit/TransactionTest.php b/Spanner/tests/Unit/TransactionTest.php index fbbf361386dc..ce4c18ad08dd 100644 --- a/Spanner/tests/Unit/TransactionTest.php +++ b/Spanner/tests/Unit/TransactionTest.php @@ -19,7 +19,6 @@ use Google\ApiCore\ValidationException; use Google\Cloud\Core\ApiHelperTrait; -use Google\Cloud\Spanner\Serializer; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\TimeTrait; use Google\Cloud\Spanner\BatchDmlResult; @@ -27,6 +26,7 @@ use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; use Google\Cloud\Spanner\Result; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\Session; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; use Google\Cloud\Spanner\Timestamp; diff --git a/Spanner/tests/Unit/TransactionTypeTest.php b/Spanner/tests/Unit/TransactionTypeTest.php index cd631d1f072a..050380da134c 100644 --- a/Spanner/tests/Unit/TransactionTypeTest.php +++ b/Spanner/tests/Unit/TransactionTypeTest.php @@ -17,7 +17,6 @@ namespace Google\Cloud\Spanner\Tests\Unit; -use Google\Cloud\Spanner\Serializer; use Google\ApiCore\ServerStream; use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Testing\GrpcTestTrait; @@ -28,6 +27,7 @@ use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\KeySet; use Google\Cloud\Spanner\Operation; +use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\Cloud\Spanner\Snapshot; use Google\Cloud\Spanner\Tests\ResultGeneratorTrait; @@ -263,7 +263,7 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'minReadTimestamp' => $this->protoTimestamp->__debugInfo(), + 'minReadTimestamp' => $this->timestamp->formatForApi(), 'maxStaleness' => $duration, ] ] @@ -333,7 +333,7 @@ public function testDatabaseSnapshotSingleUseReadTimestampAndExactStaleness($chu $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'readTimestamp' => $this->protoTimestamp->__debugInfo(), + 'readTimestamp' => $this->timestamp->formatForApi(), 'exactStaleness' => $duration, ] ] @@ -371,7 +371,7 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); $options = [ 'readOnly' => [ - 'readTimestamp' => $this->protoTimestamp->__debugInfo(), + 'readTimestamp' => $this->timestamp->formatForApi(), 'exactStaleness' => $duration, ] ]; From f86831134e3bca5cb6a51623587e256b2b95b67d Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Thu, 21 Nov 2024 07:21:11 -0800 Subject: [PATCH 07/12] add SpannerMiddleware --- Spanner/src/Backup.php | 26 +++-- Spanner/src/Database.php | 81 +++++++------- Spanner/src/Instance.php | 31 +++--- Spanner/src/InstanceConfiguration.php | 28 ++--- .../src/Middleware/SpannerMiddleware.php | 31 +++++- Spanner/src/Operation.php | 105 +++++++++--------- Spanner/src/RequestTrait.php | 39 ------- Spanner/src/Session/Session.php | 16 +-- Spanner/src/SpannerClient.php | 24 ++-- Spanner/src/TransactionalReadTrait.php | 19 +--- Spanner/tests/Unit/DatabaseTest.php | 10 +- Spanner/tests/Unit/TransactionTest.php | 16 +-- 12 files changed, 198 insertions(+), 228 deletions(-) rename Core/src/Middleware/ExceptionMiddleware.php => Spanner/src/Middleware/SpannerMiddleware.php (74%) diff --git a/Spanner/src/Backup.php b/Spanner/src/Backup.php index 27dbd8261ffb..389f87480d91 100644 --- a/Spanner/src/Backup.php +++ b/Spanner/src/Backup.php @@ -124,9 +124,9 @@ public function create( } $request = $this->serializer->decodeMessage(new CreateBackupRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); - - return $this->databaseAdminClient->createBackup($request, $callOptions) + return $this->databaseAdminClient->createBackup($request, $callOptions + [ + 'resource-prefix' => $this->instance->name(), + ]) ->withResultFunction($this->backupResultFunction()); } @@ -168,9 +168,10 @@ public function createCopy( ]; $request = $this->serializer->decodeMessage(new CopyBackupRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); - return $this->databaseAdminClient->copyBackup($request, $callOptions) + return $this->databaseAdminClient->copyBackup($request, $callOptions + [ + 'resource-prefix' => $this->instance->name(), + ]) ->withResultFunction($this->backupResultFunction()); } @@ -193,9 +194,10 @@ public function delete(array $options = []): void ]; $request = $this->serializer->decodeMessage(new DeleteBackupRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $this->databaseAdminClient->deleteBackup($request, $callOptions); + $this->databaseAdminClient->deleteBackup($request, $callOptions + [ + 'resource-prefix' => $this->name, + ]); } /** @@ -277,9 +279,10 @@ public function reload(array $options = []): array ]; $request = $this->serializer->decodeMessage(new GetBackupRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $response = $this->databaseAdminClient->getBackup($request, $callOptions); + $response = $this->databaseAdminClient->getBackup($request, $callOptions + [ + 'resource-prefix' => $this->name, + ]); return $this->info = $this->handleResponse($response); } @@ -340,9 +343,10 @@ public function updateExpireTime(DateTimeInterface $newTimestamp, array $options ]; $request = $this->serializer->decodeMessage(new UpdateBackupRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $response = $this->databaseAdminClient->updateBackup($request, $callOptions); + $response = $this->databaseAdminClient->updateBackup($request, $callOptions + [ + 'resource-prefix' => $this->name, + ]); return $this->info = $this->handleResponse($response); } diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 8e3c89678a11..60f111224652 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -358,9 +358,10 @@ public function reload(array $options = []): array $data['name'] = $this->name; $request = $this->serializer->decodeMessage(new GetDatabaseRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $response = $this->databaseAdminClient->getDatabase($request, $callOptions); + $response = $this->databaseAdminClient->getDatabase($request, $callOptions + [ + 'resource-prefix' => $this->name, + ]); return $this->info = $this->handleResponse($response); } @@ -423,9 +424,9 @@ public function create(array $options = []): OperationResponse ]; $request = $this->serializer->decodeMessage(new CreateDatabaseRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); - - return $this->databaseAdminClient->createDatabase($request, $callOptions) + return $this->databaseAdminClient->createDatabase($request, $callOptions + [ + 'resource-prefix' => $this->instance->name(), + ]) ->withResultFunction($this->databaseResultFunction()); } @@ -488,9 +489,10 @@ public function updateDatabase(array $options = []): OperationResponse ]; $request = $this->serializer->decodeMessage(new UpdateDatabaseRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->databaseAdminClient->updateDatabase($request, $callOptions) + return $this->databaseAdminClient->updateDatabase($request, $callOptions + [ + 'resource-prefix' => $this->name, + ]) ->withResultFunction($this->databaseResultFunction()); } @@ -563,9 +565,9 @@ public function updateDdlBatch(array $statements, array $options = []): Operatio ]; $request = $this->serializer->decodeMessage(new UpdateDatabaseDdlRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - - return $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions); + return $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); } /** @@ -596,9 +598,10 @@ public function drop(array $options = []): void $data['database'] = $this->name; $request = $this->serializer->decodeMessage(new DropDatabaseRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $this->databaseAdminClient->dropDatabase($request, $callOptions); + $this->databaseAdminClient->dropDatabase($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); if ($this->sessionPool) { $this->sessionPool->clear(); @@ -633,9 +636,10 @@ public function ddl(array $options = []): array $data['database'] = $this->name; $request = $this->serializer->decodeMessage(new GetDatabaseDdlRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $response = $this->databaseAdminClient->getDatabaseDdl($request, $callOptions); + $response = $this->databaseAdminClient->getDatabaseDdl($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); $ddl = $this->handleResponse($response); if (isset($ddl['statements'])) { @@ -1683,7 +1687,6 @@ public function execute($sql, array $options = []): Result $options['transaction'], $options['transactionContext'] ) = $this->transactionSelector($options); - $options = $this->addLarHeader($options, true, $options['transactionContext']); $options['directedReadOptions'] = $this->configureDirectedReadOptions( $options, @@ -1693,7 +1696,9 @@ public function execute($sql, array $options = []): Result try { // Unset the internal flag. unset($options['singleUse']); - return $this->operation->execute($session, $sql, $options); + return $this->operation->execute($session, $sql, $options + [ + 'route-to-leader' => $options['transactionContext'] === SessionPoolInterface::CONTEXT_READWRITE + ]); } finally { $session->setExpiration(); } @@ -1782,10 +1787,11 @@ public function batchWrite(array $mutationGroups, array $options = []): \Generat ]; $request = $this->serializer->decodeMessage(new BatchWriteRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $response = $this->spannerClient->batchWrite($request, $callOptions); + $response = $this->spannerClient->batchWrite($request, $callOptions + [ + 'resource-prefix' => $this->name, + 'route-to-leader' => $this->routeToLeader, + ]); return $this->handleResponse($response); } finally { $this->isRunningTransaction = false; @@ -1927,11 +1933,10 @@ public function executePartitionedUpdate($statement, array $options = []): int } $transaction = $this->operation->transaction($session, $beginTransactionOptions); - $options = $this->addLarHeader($options); - try { return $this->operation->executeUpdate($session, $transaction, $statement, [ - 'statsItem' => 'rowCountLowerBound' + 'statsItem' => 'rowCountLowerBound', + 'route-to-leader' => true, ] + $options); } finally { $session->setExpiration(); @@ -2072,12 +2077,12 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] $this->directedReadOptions ?? [] ); - $options = $this->addLarHeader($options, true, $context); - try { // Unset the internal flag. unset($options['singleUse']); - return $this->operation->read($session, $table, $keySet, $columns, $options); + return $this->operation->read($session, $table, $keySet, $columns, $options + [ + 'route-to-leader' => $context === SessionPoolInterface::CONTEXT_READ + ]); } finally { $session->setExpiration(); } @@ -2203,10 +2208,10 @@ public function batchCreateSessions(array $options): array $data['database'] = $this->name; $request = $this->serializer->decodeMessage(new BatchCreateSessionsRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - - $response = $this->spannerClient->batchCreateSessions($request, $callOptions); + $response = $this->spannerClient->batchCreateSessions($request, $callOptions + [ + 'resource-prefix' => $this->name, + 'route-to-leader' => $this->routeToLeader + ]); return $this->handleResponse($response); } @@ -2225,9 +2230,9 @@ public function deleteSessionAsync(array $options): PromiseInterface [$data, $callOptions] = $this->splitOptionalArgs($options); $request = $this->serializer->decodeMessage(new DeleteSessionRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - - return $this->spannerClient->deleteSessionAsync($request, $callOptions); + return $this->spannerClient->deleteSessionAsync($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); } /** @@ -2254,14 +2259,13 @@ public function deleteSessionAsync(array $options): PromiseInterface public function backupOperations(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); $request = $this->serializer->decodeMessage(new ListBackupOperationsRequest(), $data); $request->setParent($this->instance->name()); return $this->buildLongRunningIterator( [$this->databaseAdminClient, 'listBackupOperations'], $request, - $callOptions + $callOptions + ['resource-prefix' => $this->name] ); } @@ -2286,9 +2290,9 @@ public function createDatabaseFromBackup($name, $backup, array $options = []): O ]; $request = $this->serializer->decodeMessage(new RestoreDatabaseRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - - return $this->databaseAdminClient->restoreDatabase($request, $callOptions) + return $this->databaseAdminClient->restoreDatabase($request, $callOptions + [ + 'resource-prefix' => $this->name + ]) ->withResultFunction($this->databaseResultFunction()); } @@ -2316,14 +2320,13 @@ public function createDatabaseFromBackup($name, $backup, array $options = []): O public function databaseOperations(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); $request = $this->serializer->decodeMessage(new ListDatabaseOperationsRequest(), $data); $request->setParent($this->instance->name()); return $this->buildLongRunningIterator( [$this->databaseAdminClient, 'listDatabaseOperations'], $request, - $callOptions + $callOptions + ['resource-prefix' => $this->name] ); } diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index 034b41fee091..e9365607e1d7 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -198,9 +198,10 @@ public function exists(array $options = []): bool 'fieldMask' => ['paths' => ['name']], ]; $request = $this->serializer->decodeMessage(new GetInstanceRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - $this->instanceAdminClient->getInstance($request, $callOptions); + $this->instanceAdminClient->getInstance($request, $callOptions + [ + 'resource-prefix' => $this->projectName + ]); } else { $this->reload($options); } @@ -252,9 +253,10 @@ public function reload(array $options = []): array } $request = $this->serializer->decodeMessage(new GetInstanceRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); - $response = $this->instanceAdminClient->getInstance($request, $callOptions); + $response = $this->instanceAdminClient->getInstance($request, $callOptions + [ + 'resource-prefix' => $this->projectName + ]); return $this->info = $this->handleResponse($response); } @@ -303,9 +305,10 @@ public function create(InstanceConfiguration $config, array $options = []): Oper ]; $request = $this->serializer->decodeMessage(new CreateInstanceRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->instanceAdminClient->createInstance($request, $callOptions) + return $this->instanceAdminClient->createInstance($request, $callOptions + [ + 'resource-prefix' => $this->name + ]) ->withResultFunction($this->instanceResultFunction()); } @@ -382,9 +385,10 @@ public function update(array $options = []): OperationResponse ]; $request = $this->serializer->decodeMessage(new UpdateInstanceRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->instanceAdminClient->updateInstance($request, $callOptions) + return $this->instanceAdminClient->updateInstance($request, $callOptions + [ + 'resource-prefix' => $this->name + ]) ->withResultFunction($this->instanceResultFunction()); } @@ -409,9 +413,10 @@ public function delete(array $options = []): void $data['name'] = $this->name; $request = $this->serializer->decodeMessage(new DeleteInstanceRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - $this->instanceAdminClient->deleteInstance($request, $callOptions); + $this->instanceAdminClient->deleteInstance($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); } /** @@ -539,12 +544,11 @@ public function databases(array $options = []): ItemIterator $data['parent'] = $this->name; $request = $this->serializer->decodeMessage(new ListDatabasesRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); return $this->buildListItemsIterator( [$this->databaseAdminClient, 'listDatabases'], $request, - $callOptions, + $callOptions + ['resource-prefix' => $this->name], function (array $database) { return $this->database($database['name'], ['database' => $database]); }, @@ -614,12 +618,11 @@ public function backups(array $options = []): ItemIterator $data['parent'] = $this->name; $request = $this->serializer->decodeMessage(new ListBackupsRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); return $this->buildListItemsIterator( [$this->databaseAdminClient, 'listBackups'], $request, - $callOptions, + $callOptions + ['resource-prefix' => $this->name], function (array $backup) { return $this->backup($backup['name'], $backup); }, diff --git a/Spanner/src/InstanceConfiguration.php b/Spanner/src/InstanceConfiguration.php index 9f5ba1774d79..c58ecc61da48 100644 --- a/Spanner/src/InstanceConfiguration.php +++ b/Spanner/src/InstanceConfiguration.php @@ -170,15 +170,12 @@ public function reload(array $options = []) { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += ['name' => $this->name]; - $callOptions = $this->addResourcePrefixHeader( - $callOptions, - InstanceAdminClient::projectName($this->projectId) - ); - $response = $this->instanceAdminClient->getInstanceConfig( - $this->serializer->decodeMessage(new GetInstanceConfigRequest(), $data), - $callOptions - ); + $request = $this->serializer->decodeMessage(new GetInstanceConfigRequest(), $data); + + $response = $this->instanceAdminClient->getInstanceConfig($request, $callOptions + [ + 'resource-prefix' => InstanceAdminClient::projectName($this->projectId), + ]); return $this->info = $this->handleResponse($response); } @@ -241,11 +238,10 @@ public function create(InstanceConfiguration $baseConfig, array $replicas, array new CreateInstanceConfigRequest(), $requestArray ); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); $operationResponse = $this->instanceAdminClient->createInstanceConfig( $request, - $callOptions + $callOptions + ['resource-prefix' => $this->name] ); return $operationResponse @@ -291,11 +287,10 @@ public function update(array $options = []) 'updateMask' => $this->fieldMask($data), 'validateOnly' => $validateOnly ]); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); $operationResponse = $this->instanceAdminClient->updateInstanceConfig( $request, - $callOptions + $callOptions + ['resource-prefix' => $this->name] ); return $operationResponse @@ -323,10 +318,11 @@ public function delete(array $options = []) [$data, $callOptions] = $this->splitOptionalArgs($options); $data += ['name' => $this->name]; - $this->instanceAdminClient->deleteInstanceConfig( - $this->serializer->decodeMessage(new DeleteInstanceConfigRequest(), $data), - $this->addResourcePrefixHeader($callOptions, $this->name) - ); + $request = $this->serializer->decodeMessage(new DeleteInstanceConfigRequest(), $data); + + $this->instanceAdminClient->deleteInstanceConfig($request, $callOptions + [ + 'resource-prefix' => $this->name + ]); } /** diff --git a/Core/src/Middleware/ExceptionMiddleware.php b/Spanner/src/Middleware/SpannerMiddleware.php similarity index 74% rename from Core/src/Middleware/ExceptionMiddleware.php rename to Spanner/src/Middleware/SpannerMiddleware.php index 212c76acc2c1..1bfca02c829f 100644 --- a/Core/src/Middleware/ExceptionMiddleware.php +++ b/Spanner/src/Middleware/SpannerMiddleware.php @@ -30,32 +30,42 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -namespace Google\Cloud\Core\Middleware; +namespace Google\Cloud\Spanner\Middleware; +use Google\ApiCore\ArrayTrait; use Google\ApiCore\ApiException; use Google\ApiCore\BidiStream; use Google\ApiCore\Call; use Google\ApiCore\ClientStream; use Google\ApiCore\Middleware\MiddlewareInterface; -use Google\ApiCore\Serializer; +use Google\Cloud\Spanner\Serializer; use Google\ApiCore\ServerStream; use Google\Cloud\Core\RequestProcessorTrait; use GuzzleHttp\Promise\PromiseInterface; use Throwable; /** - * Middleware that wraps any Api Exception to a `Google\Cloud\Core\Exception` - * exception class. This is primarily to maintain backwards compatibility with - * previous Spanner versions. + * Middleware for Spanner that adds the following functionality: + * + * - Wraps any Api Exception to a `Google\Cloud\Core\Exception` exception + * class. This is primarily to maintain backwards compatibility with previous + * Spanner versions. + * - * * @internal */ -class ExceptionMiddleware implements MiddlewareInterface +class SpannerMiddleware implements MiddlewareInterface { + use ArrayTrait; + + private const ROUTE_TO_LEADER_HEADER = 'x-goog-spanner-route-to-leader'; + private const RESOURCE_PREFIX_HEADER = 'google-cloud-resource-prefix'; + use RequestProcessorTrait; /** @var callable */ private $nextHandler; + private $serializer; public function __construct(callable $nextHandler) { @@ -71,6 +81,14 @@ public function __construct(callable $nextHandler) */ public function __invoke(Call $call, array $options) { + if ($resourcePrefix = $this->pluck('resource-prefix', $options, false)) { + $options['headers'][self::RESOURCE_PREFIX_HEADER] = [$options['resource-prefix']]; + } + + if (true === $this->pluck('route-to-leader', $options, false)) { + $options['headers'][self::ROUTE_TO_LEADER_HEADER] = ['true']; + } + $response = ($this->nextHandler)($call, $options); if ($response instanceof PromiseInterface) { return $response->then(null, function ($value) { @@ -82,6 +100,7 @@ public function __invoke(Call $call, array $options) } }); } + // this can also be a Stream return $response; } diff --git a/Spanner/src/Operation.php b/Spanner/src/Operation.php index 22451bd377c7..283f5629b29d 100644 --- a/Spanner/src/Operation.php +++ b/Spanner/src/Operation.php @@ -17,6 +17,8 @@ namespace Google\Cloud\Spanner; +use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Core\RequestProcessorTrait; use Google\Cloud\Spanner\Batch\QueryPartition; use Google\Cloud\Spanner\Batch\ReadPartition; use Google\Cloud\Spanner\Session\Session; @@ -47,7 +49,8 @@ */ class Operation { - use RequestTrait; + use ApiHelperTrait; + use RequestProcessorTrait; use MutationTrait; const OP_INSERT = 'insert'; @@ -163,10 +166,10 @@ public function commitWithResponse(Session $session, array $mutations, array $op $data = $this->formatSingleUseTransactionOptions($data); $request = $this->serializer->decodeMessage(new CommitRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - - $response = $this->spannerClient->commit($request, $callOptions); + $response = $this->spannerClient->commit($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $this->routeToLeader + ]); $timestamp = $response->getCommitTimestamp(); return [ @@ -202,10 +205,10 @@ public function rollback(Session $session, $transactionId, array $options = []): ]; $request = $this->serializer->decodeMessage(new RollbackRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - - $this->spannerClient->rollback($request, $callOptions); + $this->spannerClient->rollback($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $this->routeToLeader + ]); } /** @@ -377,10 +380,10 @@ public function executeUpdateBatch( ]; $request = $this->serializer->decodeMessage(new ExecuteBatchDmlRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - - $response = $this->spannerClient->executeBatchDml($request, $callOptions); + $response = $this->spannerClient->executeBatchDml($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $this->routeToLeader + ]); $res = $this->handleResponse($response); if (empty($transaction->id())) { @@ -663,10 +666,10 @@ public function createSession($databaseName, array $options = []): Session $request = $this->serializer->decodeMessage(new CreateSessionRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - - $response = $this->spannerClient->createSession($request, $callOptions); + $response = $this->spannerClient->createSession($request, $callOptions + [ + 'resource-prefix' => $databaseName, + 'route-to-leader' => $this->routeToLeader + ]); $res = $this->handleResponse($response); return $this->session($res['name']); @@ -753,10 +756,11 @@ public function partitionQuery( ]; $request = $this->serializer->decodeMessage(new PartitionQueryRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $response = $this->spannerClient->partitionQuery($request, $callOptions); + $response = $this->spannerClient->partitionQuery($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $this->routeToLeader + ]); $res = $this->handleResponse($response); $partitions = []; @@ -816,10 +820,11 @@ public function partitionRead( ]; $request = $this->serializer->decodeMessage(new PartitionReadRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $response = $this->spannerClient->partitionRead($request, $callOptions); + $response = $this->spannerClient->partitionRead($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $this->routeToLeader + ]); $res = $this->handleResponse($response); $partitions = []; @@ -866,19 +871,21 @@ private function beginTransaction(Session $session, array $options = []): array $transactionOptions = $this->formatTransactionOptions( $this->pluck('transactionOptions', $data, false) ?: [] ); - if (isset($transactionOptions['readWrite']) - || isset($transactionOptions['partitionedDml'])) { - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - } + $routeToLeader = ( + isset($transactionOptions['readWrite']) || isset($transactionOptions['partitionedDml']) + ) && $this->routeToLeader; + $data += [ 'session' => $session->name(), 'options' => $transactionOptions ]; $request = $this->serializer->decodeMessage(new BeginTransactionRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); - $response = $this->spannerClient->beginTransaction($request, $callOptions); + $response = $this->spannerClient->beginTransaction($request, $callOptions + [ + 'resource-prefix' => $this->getDatabaseNameFromSession($session), + 'route-to-leader' => $routeToLeader, + ]); return $this->handleResponse($response); } @@ -1066,17 +1073,21 @@ private function formatTransactionOptions(array $transactionOptions): array */ private function executeStreamingSql(array $args) { - list($data, $callOptions) = $this->splitOptionalArgs($args); + list($data, $callOptions) = $this->splitOptionalArgs($args, ['route-to-leader']); $data = $this->formatSqlParams($data); $data['transaction'] = $this->createTransactionSelector($data); $data['queryOptions'] = $this->createQueryOptions($data); - $callOptions = $this->conditionallyUnsetLarHeader($callOptions, $this->routeToLeader); + if (!$this->routeToLeader) { + unset($callOptions['route-to-leader']); + } + $databaseName = $this->pluck('database', $data); $request = $this->serializer->decodeMessage(new ExecuteSqlRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); - $response = $this->spannerClient->executeStreamingSql($request, $callOptions); + $response = $this->spannerClient->executeStreamingSql($request, $callOptions + [ + 'resource-prefix' => $databaseName, + ]); return $this->handleResponse($response); } @@ -1086,15 +1097,18 @@ private function executeStreamingSql(array $args) */ private function streamingRead(array $args): \Generator { - list($data, $callOptions) = $this->splitOptionalArgs($args); + list($data, $callOptions) = $this->splitOptionalArgs($args, ['route-to-leader']); $data['transaction'] = $this->createTransactionSelector($data); - $callOptions = $this->conditionallyUnsetLarHeader($callOptions, $this->routeToLeader); + if (!$this->routeToLeader) { + unset($callOptions['route-to-leader']); + } $databaseName = $this->pluck('database', $data); $request = $this->serializer->decodeMessage(new ReadRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $databaseName); - $response = $this->spannerClient->streamingRead($request, $callOptions); + $response = $this->spannerClient->streamingRead($request, $callOptions + [ + 'resource-prefix' => $databaseName, + ]); return $this->handleResponse($response); } @@ -1132,23 +1146,6 @@ private function formatPartitionQueryOptions(array $args): array return $args; } - /** - * Conditionally unset the LAR header. - * - * @param array $args Request arguments. - * @param bool $value Whether to set or unset the LAR header. - * @return array - */ - private function conditionallyUnsetLarHeader( - array $args, - bool $value = true - ): array { - if (!$value) { - unset($args['headers'][$this->larHeader]); - } - return $args; - } - /** * Represent the class in a more readable and digestable fashion. * diff --git a/Spanner/src/RequestTrait.php b/Spanner/src/RequestTrait.php index e335cf094668..10d9d3e1dcbd 100644 --- a/Spanner/src/RequestTrait.php +++ b/Spanner/src/RequestTrait.php @@ -36,45 +36,6 @@ trait RequestTrait use ApiHelperTrait; use RequestProcessorTrait; - private $larHeader = 'x-goog-spanner-route-to-leader'; - private $resourcePrefixHeader = 'google-cloud-resource-prefix'; - - /** - * Add the `x-goog-spanner-route-to-leader` header value to the request. - * - * @param array $args Request arguments. - * @param bool $value LAR header value. - * @param string $context Transaction context. - * @return array - */ - private function addLarHeader( - array $args, - bool $value = true, - string $context = SessionPoolInterface::CONTEXT_READWRITE - ) { - if (!$value) { - return $args; - } - // If value is true and context is READWRITE, set LAR header. - if ($context === SessionPoolInterface::CONTEXT_READWRITE) { - $args['headers'][$this->larHeader] = ['true']; - } - return $args; - } - - /** - * Add the `google-cloud-resource-prefix` header value to the request. - * - * @param array $args Request arguments. - * @param string $value Resource prefix header value. - * @return array - */ - private function addResourcePrefixHeader(array $args, string $value) - { - $args['headers'][$this->resourcePrefixHeader] = [$value]; - return $args; - } - /** * Helper making list calls for long running operations. * diff --git a/Spanner/src/Session/Session.php b/Spanner/src/Session/Session.php index f293d8e12c5a..a081d4b0c76a 100644 --- a/Spanner/src/Session/Session.php +++ b/Spanner/src/Session/Session.php @@ -17,9 +17,9 @@ namespace Google\Cloud\Spanner\Session; +use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Spanner\Database; -use Google\Cloud\Spanner\RequestTrait; use Google\Cloud\Spanner\Serializer; use Google\Cloud\Spanner\V1\Client\SpannerClient; use Google\Cloud\Spanner\V1\DeleteSessionRequest; @@ -30,7 +30,7 @@ */ class Session { - use RequestTrait; + use ApiHelperTrait; /** * @var int|null @@ -117,10 +117,11 @@ public function exists(array $options = []) try { $request = $this->serializer->decodeMessage(new GetSessionRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->databaseName); - $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - $this->spannerClient->getSession($request, $callOptions); + $this->spannerClient->getSession($request, $callOptions + [ + 'resource-prefix' => $this->databaseName, + 'route-to-leader' => $this->routeToLeader, + ]); } catch (NotFoundException $e) { return false; } @@ -141,9 +142,10 @@ public function delete(array $options = []) ]; $request = $this->serializer->decodeMessage(new DeleteSessionRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->databaseName); - $this->spannerClient->deleteSession($request, $callOptions); + $this->spannerClient->deleteSession($request, $callOptions + [ + 'resource-prefix' => $this->databaseName, + ]); } /** diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index 8b3f9655cdb6..600d1b82a554 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -34,6 +34,7 @@ use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigsRequest; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstancesRequest; use Google\Cloud\Spanner\Admin\Instance\V1\ReplicaInfo; +use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; use Google\Cloud\Spanner\Batch\BatchClient; use Google\Cloud\Spanner\Middleware\SpannerMiddleware; use Google\Cloud\Spanner\Session\SessionPoolInterface; @@ -255,17 +256,21 @@ public function __construct(array $config = []) 'libName' => 'gccl', 'serializer' => $this->serializer, ]; - $middleware = function (MiddlewareInterface $handler) { - return new SpannerMiddleware($handler); - }; $this->spannerClient = $config['gapicSpannerClient'] ?? new GapicSpannerClient($clientConfig); - $this->spannerClient->addMiddleware($middleware); $this->instanceAdminClient = $config['gapicSpannerInstanceAdminClient'] ?? new InstanceAdminClient($clientConfig); - $this->instanceAdminClient->addMiddleware($middleware); $this->databaseAdminClient = $config['gapicSpannerDatabaseAdminClient'] ?? new DatabaseAdminClient($clientConfig); + + // Add the SpannerMiddleware, which wraps API Exceptions, and adds + // Resource Prefix and LAR headers + $middleware = function (MiddlewareInterface $handler) { + return new SpannerMiddleware($handler); + }; + $this->spannerClient->addMiddleware($middleware); + $this->instanceAdminClient->addMiddleware($middleware); $this->databaseAdminClient->addMiddleware($middleware); + $this->projectName = InstanceAdminClient::projectName($this->projectId); } @@ -410,12 +415,11 @@ public function instanceConfigurations(array $options = []) $data['parent'] = $this->projectName; $request = $this->serializer->decodeMessage(new ListInstanceConfigsRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); return $this->buildListItemsIterator( [$this->instanceAdminClient, 'listInstanceConfigs'], $request, - $callOptions, + $callOptions + ['resource-prefix' => $this->projectName], function (array $config) { return $this->instanceConfiguration($config['name'], $config); }, @@ -486,14 +490,13 @@ public function instanceConfiguration($name, array $options = []) public function instanceConfigOperations(array $options = []) { [$data, $callOptions] = $this->splitOptionalArgs($options); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); $request = $this->serializer->decodeMessage(new ListInstanceConfigOperationsRequest(), $data); $request->setParent($this->projectName); return $this->buildLongRunningIterator( [$this->instanceAdminClient, 'listInstanceConfigOperations'], $request, - $callOptions, + $callOptions + ['resource-prefix' => $this->projectName], function (Operation $operation) { return $this->resumeOperation( $operation->getName(), @@ -598,12 +601,11 @@ public function instances(array $options = []) $data += ['filter' => '', 'parent' => $this->projectName]; $request = $this->serializer->decodeMessage(new ListInstancesRequest(), $data); - $callOptions = $this->addResourcePrefixHeader($callOptions, $this->projectName); return $this->buildListItemsIterator( [$this->instanceAdminClient, 'listInstances'], $request, - $callOptions, + $callOptions + ['resource-prefix' => $this->projectName], function (array $instance) { $name = InstanceAdminClient::parseName($instance['name'])['instance']; return $this->instance($name, $instance); diff --git a/Spanner/src/TransactionalReadTrait.php b/Spanner/src/TransactionalReadTrait.php index b80415e3ce82..c9b29e72a214 100644 --- a/Spanner/src/TransactionalReadTrait.php +++ b/Spanner/src/TransactionalReadTrait.php @@ -26,7 +26,6 @@ trait TransactionalReadTrait { use TransactionConfigurationTrait; - // use RequestTrait; /** * @var Operation @@ -303,15 +302,12 @@ public function execute($sql, array $options = []) $this->directedReadOptions ?? [] ); - if ($this->context === SessionPoolInterface::CONTEXT_READWRITE) { - // add LAR header - $options['headers']['x-goog-spanner-route-to-leader'] = ['true']; - } - // Unsetting the internal flag unset($options['singleUse']); - $result = $this->operation->execute($this->session, $sql, $options); + $result = $this->operation->execute($this->session, $sql, $options + [ + 'route-to-leader' => $this->context === SessionPoolInterface::CONTEXT_READWRITE + ]); if (empty($this->id()) && $result->transaction()) { $this->setId($result->transaction()->id()); @@ -389,12 +385,9 @@ public function read($table, KeySet $keySet, array $columns, array $options = [] $this->directedReadOptions ?? [] ); - if ($this->context === SessionPoolInterface::CONTEXT_READWRITE) { - // add LAR header - $options['headers']['x-goog-spanner-route-to-leader'] = ['true']; - } - - $result = $this->operation->read($this->session, $table, $keySet, $columns, $options); + $result = $this->operation->read($this->session, $table, $keySet, $columns, $options + [ + 'route-to-leader' => $this->context === SessionPoolInterface::CONTEXT_READWRITE + ]); if (empty($this->id()) && $result->transaction()) { $this->setId($result->transaction()->id()); } diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index 35d355a3fd07..99c4037f8d93 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -1332,9 +1332,8 @@ public function testExecute() return $request->getSql() == $sql; }), Argument::that(function ($callOptions) { - $this->assertArrayHasKey('headers', $callOptions); - $this->assertArrayHasKey('x-goog-spanner-route-to-leader', $callOptions['headers']); - $this->assertEquals(['true'], $callOptions['headers']['x-goog-spanner-route-to-leader']); + $this->assertArrayHasKey('route-to-leader', $callOptions); + $this->assertEquals(true, $callOptions['route-to-leader']); return true; }) ) @@ -1428,9 +1427,8 @@ public function testExecutePartitionedUpdate() return true; }), Argument::that(function ($callOptions) { - $this->assertArrayHasKey('headers', $callOptions); - $this->assertArrayHasKey('x-goog-spanner-route-to-leader', $callOptions['headers']); - $this->assertEquals(['true'], $callOptions['headers']['x-goog-spanner-route-to-leader']); + $this->assertArrayHasKey('route-to-leader', $callOptions); + $this->assertEquals(true, $callOptions['route-to-leader']); return true; }) ) diff --git a/Spanner/tests/Unit/TransactionTest.php b/Spanner/tests/Unit/TransactionTest.php index ce4c18ad08dd..bc00c7f32875 100644 --- a/Spanner/tests/Unit/TransactionTest.php +++ b/Spanner/tests/Unit/TransactionTest.php @@ -130,10 +130,7 @@ public function testExecute() return true; }), Argument::that(function (array $callOptions) { - $this->assertEquals( - $callOptions['headers']['x-goog-spanner-route-to-leader'], - ['true'] - ); + $this->assertEquals($callOptions['route-to-leader'], true); return true; }) ) @@ -256,10 +253,7 @@ public function testExecuteUpdateBatch() return true; }), Argument::that(function (array $callOptions) { - $this->assertEquals( - $callOptions['headers']['x-goog-spanner-route-to-leader'], - ['true'] - ); + $this->assertEquals($callOptions['route-to-leader'], true); return true; }) ) @@ -427,10 +421,8 @@ public function testRead() return true; }), Argument::that(function (array $callOptions) { - $this->assertEquals( - $callOptions['headers']['x-goog-spanner-route-to-leader'], - ['true'] - ); + $this->assertArrayHasKey('route-to-leader', $callOptions); + $this->assertEquals(true, $callOptions['route-to-leader']); return true; }) From 2b1ac1c3e9e6c01107744028bb9384d285627a8d Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 25 Nov 2024 06:50:07 -0800 Subject: [PATCH 08/12] fix tests --- Spanner/tests/System/AdminTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Spanner/tests/System/AdminTest.php b/Spanner/tests/System/AdminTest.php index 1bc9649ce56c..b8c5ffee6f2b 100644 --- a/Spanner/tests/System/AdminTest.php +++ b/Spanner/tests/System/AdminTest.php @@ -78,7 +78,10 @@ public function testInstance() 'nodeCount' => 0, 'processingUnits' => 0, 'state' => Instance::STATE_READY, - 'config' => '' + 'config' => '', + 'replicaComputeCapacity' => [], + 'edition' => 0, + 'defaultBackupScheduleType' => 0, ]; $info = $instance->reload(['fieldMask' => $requestedFieldNames]); $this->assertEquals($expectedInfo, $info); @@ -139,7 +142,8 @@ public function testDatabaseDropProtection() $op = $instance->createDatabase($dbName); $this->assertInstanceOf(OperationResponse::class, $op); - $db = $op->pollUntilComplete(); + $op->pollUntilComplete(); + $db = $op->getResult(); $this->assertInstanceOf(Database::class, $db); $info = $db->reload(); From cc7eec7533551ba7ca6091aa3485317348db9305 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Mon, 25 Nov 2024 14:44:15 -0800 Subject: [PATCH 09/12] use Core LongRunningOperations instead of GAX --- .../LongRunningGapicConnection.php | 117 ++++++++++++++++++ Core/src/LongRunning/LongRunningOperation.php | 7 +- Spanner/src/Backup.php | 50 ++++---- Spanner/src/Database.php | 98 +++++++++------ Spanner/src/Instance.php | 63 ++++++---- Spanner/src/InstanceConfiguration.php | 41 +++--- Spanner/src/RequestTrait.php | 15 ++- Spanner/src/SpannerClient.php | 45 +++---- Spanner/tests/Snippet/BackupTest.php | 14 +-- Spanner/tests/Snippet/DatabaseTest.php | 17 ++- .../Snippet/InstanceConfigurationTest.php | 7 +- Spanner/tests/Snippet/InstanceTest.php | 27 ++-- Spanner/tests/Snippet/SpannerClientTest.php | 15 +-- Spanner/tests/Unit/BackupTest.php | 10 +- Spanner/tests/Unit/DatabaseTest.php | 22 ++-- .../tests/Unit/InstanceConfigurationTest.php | 27 +++- Spanner/tests/Unit/InstanceTest.php | 26 ++-- Spanner/tests/Unit/SpannerClientTest.php | 21 +--- 18 files changed, 376 insertions(+), 246 deletions(-) create mode 100644 Core/src/LongRunning/LongRunningGapicConnection.php diff --git a/Core/src/LongRunning/LongRunningGapicConnection.php b/Core/src/LongRunning/LongRunningGapicConnection.php new file mode 100644 index 000000000000..1b24eb83da08 --- /dev/null +++ b/Core/src/LongRunning/LongRunningGapicConnection.php @@ -0,0 +1,117 @@ +<?php +/** + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Google\Cloud\Core\LongRunning; + +use Google\ApiCore\OperationResponse; +use Google\ApiCore\Serializer; +use Google\LongRunning\Operation; +use Google\LongRunning\ListOperationsRequest; +use Google\LongRunning\GetOperationRequest; +use Google\LongRunning\CancelOperationRequest; +use Google\LongRunning\DeleteOperationRequest; +use Google\Cloud\Core\RequestProcessorTrait; + +/** + * Defines the calls required to manage Long Running Operations using a GAPIC + * generated client. + * + * @internal + */ +class LongRunningGapicConnection implements LongRunningConnectionInterface +{ + use RequestProcessorTrait; + + public function __construct( + private $gapicClient, + private Serializer $serializer + ) { + } + + /** + * @param array $args + */ + public function get(array $args) + { + $operationResponse = $this->gapicClient->resumeOperation($args['name']); + + return $this->operationResponseToArray($operationResponse); + } + + /** + * @param array $args + */ + public function cancel(array $args) + { + $operationResponse = $this->gapicClient->resumeOperation( + $args['name'], + $args['method'] ?? null + ); + $operationResponse->cancel(); + + return $this->operationResponseToArray($operationResponse); + } + + /** + * @param array $args + */ + public function delete(array $args) + { + $operationResponse = $this->gapicClient->resumeOperation( + $args['name'], + $args['method'] ?? null + ); + $operationResponse->cancel(); + + return $this->operationResponseToArray($operationResponse); + } + + /** + * @param array $args + */ + public function operations(array $args) + { + $request = ListOperationsRequest::build($args['name'], $args['filter'] ?? null); + $response = $this->gapicClient->getOperationsClient()->listOperations($request); + + return $this->handleResponse($response); + } + + private function operationResponseToArray(OperationResponse $operationResponse) + { + $response = $this->handleResponse($operationResponse->getLastProtoResponse()); + $metaType = $response['metadata']['typeUrl']; + + // unpack result Any type + $response['response'] = $this->handleResponse($operationResponse->getResult()); + + // unpack error Any type + $response['error'] = $this->handleResponse($operationResponse->getError()); + + $metadata = $operationResponse->getMetadata(); + if ($metadata instanceof Any) { + // For some reason we aren't doing this in GAX OperationResponse (but we should) + $metadata = $metadata->unpack(); + } + $response['metadata'] = $this->handleResponse($metadata); + + // Used in LongRunningOperation to invoke callables + $response['metadata'] += ['typeUrl' => $metaType]; + + return $response; + } +} diff --git a/Core/src/LongRunning/LongRunningOperation.php b/Core/src/LongRunning/LongRunningOperation.php index d0decd5967ec..1eb17c07ad78 100644 --- a/Core/src/LongRunning/LongRunningOperation.php +++ b/Core/src/LongRunning/LongRunningOperation.php @@ -252,12 +252,11 @@ public function reload(array $options = []) $this->result = null; $this->error = null; - if (isset($res['done']) && $res['done']) { + + if (isset($res['done']) && $res['done'] && isset($res['metadata']['typeUrl'])) { $type = $res['metadata']['typeUrl']; $this->result = $this->executeDoneCallback($type, $res['response']); - $this->error = (isset($res['error'])) - ? $res['error'] - : null; + $this->error = $res['error'] ?? null; } return $this->info = $res; diff --git a/Spanner/src/Backup.php b/Spanner/src/Backup.php index 389f87480d91..09058860ce96 100644 --- a/Spanner/src/Backup.php +++ b/Spanner/src/Backup.php @@ -19,7 +19,8 @@ use Closure; use DateTimeInterface; -use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\LongRunning\LongRunningGapicConnection; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\ApiCore\ValidationException; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Iterator\ItemIterator; @@ -95,14 +96,14 @@ public function __construct( * consistent copy of the database. If not present, it will be the same * as the create time of the backup. * } - * @return OperationResponse + * @return LongRunningOperation * @throws \InvalidArgumentException */ public function create( $database, DateTimeInterface $expireTime, array $options = [] - ): OperationResponse { + ): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -124,10 +125,10 @@ public function create( } $request = $this->serializer->decodeMessage(new CreateBackupRequest(), $data); - return $this->databaseAdminClient->createBackup($request, $callOptions + [ + $operation = $this->databaseAdminClient->createBackup($request, $callOptions + [ 'resource-prefix' => $this->instance->name(), - ]) - ->withResultFunction($this->backupResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -151,14 +152,14 @@ public function create( * @param array $options [optional] { * Configuration Options. * } - * @return OperationResponse + * @return LongRunningOperation * @throws \InvalidArgumentException */ public function createCopy( Backup $newBackup, DateTimeInterface $expireTime, array $options = [] - ): OperationResponse { + ): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ 'parent' => $newBackup->instance->name(), @@ -169,10 +170,10 @@ public function createCopy( $request = $this->serializer->decodeMessage(new CopyBackupRequest(), $data); - return $this->databaseAdminClient->copyBackup($request, $callOptions + [ + $operation = $this->databaseAdminClient->copyBackup($request, $callOptions + [ 'resource-prefix' => $this->instance->name(), - ]) - ->withResultFunction($this->backupResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -355,19 +356,25 @@ public function updateExpireTime(DateTimeInterface $newTimestamp, array $options * * Example: * ``` - * $operation = $spanner->resumeOperation($operationName); + * $operation = $backup->resumeOperation($operationName); * ``` * * @param string $operationName The Long Running Operation name. - * @return OperationResponse + * @return LongRunningOperation */ - public function resumeOperation($operationName, array $options = []): OperationResponse + public function resumeOperation($operationName, array $options = []): LongRunningOperation { - return (new OperationResponse( + return new LongRunningOperation( + new LongRunningGapicConnection($this->databaseAdminClient, $this->serializer), $operationName, - $this->databaseAdminClient->getOperationsClient(), + [ + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata', + 'callable' => $this->backupResultFunction(), + ] + ], $options - ))->withResultFunction($this->backupResultFunction()); + ); } /** @@ -390,7 +397,7 @@ public function resumeOperation($operationName, array $options = []): OperationR * @type string $pageToken A previously-returned page token used to * resume the loading of results from a specific point. * } - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function longRunningOperations(array $options = []): ItemIterator { @@ -429,10 +436,9 @@ private function fullyQualifiedBackupName($name): string private function backupResultFunction(): Closure { - return function (BackupProto $backup) { - $name = DatabaseAdminClient::parseName($backup->getName()); - $info = $this->serializer->decodeMessage($backup); - return $this->instance->backup($name['name'], $info); + return function (array $backup) { + $name = DatabaseAdminClient::parseName($backup['name']); + return $this->instance->backup($name['name'], $backup); }; } } diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 60f111224652..26bbad6dbc75 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -17,9 +17,8 @@ namespace Google\Cloud\Spanner; -use Closure; -use Google\ApiCore\ApiException; -use Google\ApiCore\OperationResponse; +use Closure;use Google\ApiCore\ApiException; + use Google\ApiCore\RetrySettings; use Google\ApiCore\ValidationException; use Google\Cloud\Core\Exception\AbortedException; @@ -29,6 +28,8 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\RequestHandler; use Google\Cloud\Core\Retry; +use Google\Cloud\Core\LongRunning\LongRunningGapicConnection; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; @@ -291,13 +292,13 @@ public function backups(array $options = []): ItemIterator * eligible to be automatically deleted by Cloud Spanner. * @param array $options [optional] Configuration options. * - * @return OperationResponse<Backup> + * @return LongRunningOperation<Backup> */ public function createBackup( $name, \DateTimeInterface $expireTime, array $options = [] - ): OperationResponse { + ): LongRunningOperation { $backup = $this->instance->backup($name); return $backup->create($this->name(), $expireTime, $options); } @@ -410,9 +411,9 @@ public function exists(array $options = []): bool * * @type string[] $statements Additional DDL statements. * } - * @return OperationResponse<Database> + * @return LongRunningOperation<Database> */ - public function create(array $options = []): OperationResponse + public function create(array $options = []): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $dialect = $data['databaseDialect'] ?? DatabaseDialect::DATABASE_DIALECT_UNSPECIFIED; @@ -424,10 +425,10 @@ public function create(array $options = []): OperationResponse ]; $request = $this->serializer->decodeMessage(new CreateDatabaseRequest(), $data); - return $this->databaseAdminClient->createDatabase($request, $callOptions + [ + $operation = $this->databaseAdminClient->createDatabase($request, $callOptions + [ 'resource-prefix' => $this->instance->name(), - ]) - ->withResultFunction($this->databaseResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -444,9 +445,9 @@ public function create(array $options = []): OperationResponse * `projects/<project>/instances/<instance>/backups/<backup>`. * @param array $options [optional] Configuration options. * - * @return OperationResponse<Database> + * @return LongRunningOperation<Database> */ - public function restore($backup, array $options = []): OperationResponse + public function restore($backup, array $options = []): LongRunningOperation { return $this->instance->createDatabaseFromBackup($this->name, $backup, $options); } @@ -469,9 +470,9 @@ public function restore($backup, array $options = []): OperationResponse * @type bool $enableDropProtection If `true`, delete operations for Database * and Instance will be blocked. **Defaults to** `false`. * } - * @return OperationResponse<Database> + * @return LongRunningOperation<Database> */ - public function updateDatabase(array $options = []): OperationResponse + public function updateDatabase(array $options = []): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $fieldMask = []; @@ -490,10 +491,10 @@ public function updateDatabase(array $options = []): OperationResponse $request = $this->serializer->decodeMessage(new UpdateDatabaseRequest(), $data); - return $this->databaseAdminClient->updateDatabase($request, $callOptions + [ + $operation = $this->databaseAdminClient->updateDatabase($request, $callOptions + [ 'resource-prefix' => $this->name, - ]) - ->withResultFunction($this->databaseResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -519,9 +520,9 @@ public function updateDatabase(array $options = []): OperationResponse * * @param string $statement A DDL statements to run against a database. * @param array $options [optional] Configuration options. - * @return OperationResponse<Database> + * @return LongRunningOperation<Database> */ - public function updateDdl($statement, array $options = []): OperationResponse + public function updateDdl($statement, array $options = []): LongRunningOperation { return $this->updateDdlBatch([$statement], $options); } @@ -554,9 +555,9 @@ public function updateDdl($statement, array $options = []): OperationResponse * * @param string[] $statements A list of DDL statements to run against a database. * @param array $options [optional] Configuration options. - * @return OperationResponse<void> + * @return LongRunningOperation<void> */ - public function updateDdlBatch(array $statements, array $options = []): OperationResponse + public function updateDdlBatch(array $statements, array $options = []): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -565,9 +566,11 @@ public function updateDdlBatch(array $statements, array $options = []): Operatio ]; $request = $this->serializer->decodeMessage(new UpdateDatabaseDdlRequest(), $data); - return $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions + [ + $operation = $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions + [ 'resource-prefix' => $this->name ]); + + return $this->operationFromOperationResponse($operation); } /** @@ -2254,7 +2257,7 @@ public function deleteSessionAsync(array $options): PromiseInterface * been generated by a previous call to the API. * } * - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function backupOperations(array $options = []): ItemIterator { @@ -2278,9 +2281,9 @@ public function backupOperations(array $options = []): ItemIterator * `projects/<project>/instances/<instance>/backups/<backup>`. * @param array $options [optional] Configuration options. * - * @return OperationResponse + * @return LongRunningOperation */ - public function createDatabaseFromBackup($name, $backup, array $options = []): OperationResponse + public function createDatabaseFromBackup($name, $backup, array $options = []): LongRunningOperation { [$data, $callOptions] = $this->splitOptionalArgs($options); $data += [ @@ -2290,10 +2293,11 @@ public function createDatabaseFromBackup($name, $backup, array $options = []): O ]; $request = $this->serializer->decodeMessage(new RestoreDatabaseRequest(), $data); - return $this->databaseAdminClient->restoreDatabase($request, $callOptions + [ + $operation = $this->databaseAdminClient->restoreDatabase($request, $callOptions + [ 'resource-prefix' => $this->name - ]) - ->withResultFunction($this->databaseResultFunction()); + ]); + + return $this->operationFromOperationResponse($operation); } /** @@ -2315,7 +2319,7 @@ public function createDatabaseFromBackup($name, $backup, array $options = []): O * been generated by a previous call to the API. * } * - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function databaseOperations(array $options = []): ItemIterator { @@ -2339,15 +2343,25 @@ public function databaseOperations(array $options = []): ItemIterator * ``` * * @param string $operationName The Long Running Operation name. - * @return OperationResponse + * @return LongRunningOperation */ - public function resumeOperation($operationName, array $options = []) + public function resumeOperation($operationName, array $options = []): LongRunningOperation { - return (new OperationResponse( + return new LongRunningOperation( + new LongRunningGapicConnection($this->databaseAdminClient, $this->serializer), $operationName, - $this->databaseAdminClient->getOperationsClient(), + [ + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateDatabaseMetadata', + 'callable' => $this->databaseResultFunction(), + ], + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata', + 'callable' => $this->databaseResultFunction(), + ] + ], $options - ))->withResultFunction($this->databaseResultFunction()); + ); } /** @@ -2370,9 +2384,9 @@ public function resumeOperation($operationName, array $options = []) * @type string $pageToken A previously-returned page token used to * resume the loading of results from a specific point. * } - * @return PagedListResponse<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ - public function longRunningOperations(array $options = []) + public function longRunningOperations(array $options = []): ItemIterator { [$data, $callOptions] = $this->splitOptionalArgs($options); $request = $this->serializer->decodeMessage(new ListOperationsRequest(), $data); @@ -2395,8 +2409,10 @@ public function longRunningOperations(array $options = []) * @param array $options [optional] Configuration options. * @return Session */ - private function selectSession($context = SessionPoolInterface::CONTEXT_READ, array $options = []): Session - { + private function selectSession( + $context = SessionPoolInterface::CONTEXT_READ, + array $options = [] + ): Session { if ($this->session) { return $this->session; } @@ -2594,11 +2610,11 @@ private function fieldValue($param): Value private function databaseResultFunction(): Closure { - return function (DatabaseProto $database): self { - $name = DatabaseAdminClient::parseName($database->getName()); + return function (array $database): self { + $name = DatabaseAdminClient::parseName($database['name']); return $this->instance->database($name['database'], [ 'sessionPool' => $this->sessionPool, - 'database' => $this->serializer->encodeMessage($database), + 'database' => $database, 'databaseRole' => $this->databaseRole, ]); }; diff --git a/Spanner/src/Instance.php b/Spanner/src/Instance.php index e9365607e1d7..2af99c0fe309 100644 --- a/Spanner/src/Instance.php +++ b/Spanner/src/Instance.php @@ -18,7 +18,8 @@ namespace Google\Cloud\Spanner; use Closure; -use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\LongRunning\LongRunningOperation; +use Google\Cloud\Core\LongRunning\LongRunningGapicConnection; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; @@ -281,11 +282,11 @@ public function reload(array $options = []): array * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://cloudplatform.googleblog.com/2015/10/using-labels-to-organize-Google-Cloud-Platform-resources.html). * } - * @return OperationResponse + * @return LongRunningOperation * @throws \InvalidArgumentException * @codingStandardsIgnoreEnd */ - public function create(InstanceConfiguration $config, array $options = []): OperationResponse + public function create(InstanceConfiguration $config, array $options = []): LongRunningOperation { list($instance, $callOptions) = $this->splitOptionalArgs($options); $instanceId = InstanceAdminClient::parseName($this->name)['instance']; @@ -306,10 +307,10 @@ public function create(InstanceConfiguration $config, array $options = []): Oper $request = $this->serializer->decodeMessage(new CreateInstanceRequest(), $data); - return $this->instanceAdminClient->createInstance($request, $callOptions + [ + $operation = $this->instanceAdminClient->createInstance($request, $callOptions + [ 'resource-prefix' => $this->name - ]) - ->withResultFunction($this->instanceResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -367,10 +368,10 @@ public function state(array $options = []): int|null * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://goo.gl/xmQnxf). * } - * @return OperationResponse + * @return LongRunningOperation * @throws \InvalidArgumentException */ - public function update(array $options = []): OperationResponse + public function update(array $options = []): LongRunningOperation { list($instance, $callOptions) = $this->splitOptionalArgs($options); @@ -386,10 +387,10 @@ public function update(array $options = []): OperationResponse $request = $this->serializer->decodeMessage(new UpdateInstanceRequest(), $data); - return $this->instanceAdminClient->updateInstance($request, $callOptions + [ + $operation = $this->instanceAdminClient->updateInstance($request, $callOptions + [ 'resource-prefix' => $this->name - ]) - ->withResultFunction($this->instanceResultFunction()); + ]); + return $this->operationFromOperationResponse($operation); } /** @@ -439,9 +440,9 @@ public function delete(array $options = []): void * @type SessionPoolInterface $sessionPool A pool used to manage * sessions. * } - * @return OperationResponse + * @return LongRunningOperation */ - public function createDatabase($name, array $options = []): OperationResponse + public function createDatabase($name, array $options = []): LongRunningOperation { $instantiation = $this->pluckArray(['sessionPool'], $options); @@ -463,9 +464,9 @@ public function createDatabase($name, array $options = []): OperationResponse * `projects/<project>/instances/<instance>/backups/<backup>`. * @param array $options [optional] Configuration options. * - * @return OperationResponse + * @return LongRunningOperation */ - public function createDatabaseFromBackup($name, $backup, array $options = []): OperationResponse + public function createDatabaseFromBackup($name, $backup, array $options = []): LongRunningOperation { return $this->database($name)->createDatabaseFromBackup($name, $backup, $options); } @@ -655,7 +656,7 @@ function (array $backup) { * been generated by a previous call to the API. * } * - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function backupOperations(array $options = []): ItemIterator { @@ -686,7 +687,7 @@ public function backupOperations(array $options = []): ItemIterator * been generated by a previous call to the API. * } * - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function databaseOperations(array $options = []): ItemIterator { @@ -804,15 +805,25 @@ public function createInstanceArray( * ``` * * @param string $operationName The Long Running Operation name. - * @return OperationResponse + * @return LongRunningOperation */ - public function resumeOperation($operationName, array $options = []): OperationResponse + public function resumeOperation($operationName, array $options = []): LongRunningOperation { - return (new OperationResponse( + return new LongRunningOperation( + new LongRunningGapicConnection($this->instanceAdminClient, $this->serializer), $operationName, - $this->instanceAdminClient->getOperationsClient(), + [ + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceMetadata', + 'callable' => $this->instanceResultFunction() + ], + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceMetadata', + 'callable' => $this->instanceResultFunction() + ] + ], $options - ))->withResultFunction($this->instanceResultFunction()); + ); } /** @@ -835,7 +846,7 @@ public function resumeOperation($operationName, array $options = []): OperationR * @type string $pageToken A previously-returned page token used to * resume the loading of results from a specific point. * } - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function longRunningOperations(array $options = []): ItemIterator { @@ -852,8 +863,8 @@ public function longRunningOperations(array $options = []): ItemIterator private function instanceResultFunction(): Closure { - return function (InstanceProto $result) { - $name = InstanceAdminClient::parseName($result->getName()); + return function (array $result) { + $name = InstanceAdminClient::parseName($result['name']); return new self( $this->spannerClient, $this->instanceAdminClient, @@ -862,7 +873,7 @@ private function instanceResultFunction(): Closure $this->projectId, $name['instance'], $this->returnInt64AsObject, - $this->serializer->encodeMessage($result), + $result, [ 'directedReadOptions' => $this->directedReadOptions, 'routeToLeader' => $this->routeToLeader, diff --git a/Spanner/src/InstanceConfiguration.php b/Spanner/src/InstanceConfiguration.php index c58ecc61da48..e5acd961ad88 100644 --- a/Spanner/src/InstanceConfiguration.php +++ b/Spanner/src/InstanceConfiguration.php @@ -19,7 +19,8 @@ use Closure; use Google\ApiCore\ApiException; -use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\LongRunning\LongRunningOperation; +use Google\Cloud\Core\LongRunning\LongRunningGapicConnection; use Google\ApiCore\ValidationException; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; @@ -210,7 +211,7 @@ public function reload(array $options = []) * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return OperationResponse + * @return LongRunningOperation * @throws ValidationException * @codingStandardsIgnoreEnd */ @@ -239,13 +240,12 @@ public function create(InstanceConfiguration $baseConfig, array $replicas, array $requestArray ); - $operationResponse = $this->instanceAdminClient->createInstanceConfig( + $operation = $this->instanceAdminClient->createInstanceConfig( $request, $callOptions + ['resource-prefix' => $this->name] ); - return $operationResponse - ->withResultFunction($this->instanceConfigResultFunction()); + return $this->operationFromOperationResponse($operation); } /** @@ -272,7 +272,7 @@ public function create(InstanceConfiguration $baseConfig, array $replicas, array * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return OperationResponse + * @return LongRunningOperation * @throws \InvalidArgumentException */ public function update(array $options = []) @@ -288,13 +288,12 @@ public function update(array $options = []) 'validateOnly' => $validateOnly ]); - $operationResponse = $this->instanceAdminClient->updateInstanceConfig( + $operation = $this->instanceAdminClient->updateInstanceConfig( $request, $callOptions + ['resource-prefix' => $this->name] ); - return $operationResponse - ->withResultFunction($this->instanceConfigResultFunction()); + return $this->operationFromOperationResponse($operation); } /** @@ -334,15 +333,25 @@ public function delete(array $options = []) * ``` * * @param string $operationName The Long Running Operation name. - * @return OperationResponse + * @return LongRunningOperation */ public function resumeOperation($operationName, array $options = []) { - return (new OperationResponse( + return new LongRunningOperation( + new LongRunningGapicConnection($this->instanceAdminClient, $this->serializer), $operationName, - $this->instanceAdminClient->getOperationsClient(), + [ + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata', + 'callable' => $this->instanceConfigResultFunction(), + ], + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata', + 'callable' => $this->instanceConfigResultFunction(), + ] + ], $options - ))->withResultFunction($this->instanceConfigResultFunction()); + ); } /** @@ -395,14 +404,14 @@ private function fieldMask(array $instanceArray) private function instanceConfigResultFunction(): Closure { - return function (InstanceConfig $result) { - $name = InstanceAdminClient::parseName($result->getName()); + return function (array $result) { + $name = InstanceAdminClient::parseName($result['name']); return new self( $this->instanceAdminClient, $this->serializer, $this->projectId, $name['instance_config'], - $this->serializer->encodeMessage($result) + $result ); }; } diff --git a/Spanner/src/RequestTrait.php b/Spanner/src/RequestTrait.php index 10d9d3e1dcbd..f949d3b56e2b 100644 --- a/Spanner/src/RequestTrait.php +++ b/Spanner/src/RequestTrait.php @@ -18,10 +18,12 @@ namespace Google\Cloud\Spanner; use Google\ApiCore\ApiException; +use Google\ApiCore\OperationResponse; use Google\Cloud\Core\ApiHelperTrait; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Iterator\PageIterator; use Google\Cloud\Core\RequestProcessorTrait; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Session\SessionPoolInterface; use Google\LongRunning\Operation; use Google\Protobuf\Internal\Message; @@ -45,7 +47,7 @@ trait RequestTrait * @param array $callOptions [optional] Call options for the request * @param callable $resultMapper [optional] A callable to map the Operation to an * operation response. Defaults to `$this->resumeOperation()`. - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ private function buildLongRunningIterator( callable $call, @@ -59,7 +61,7 @@ private function buildLongRunningIterator( $resultMapper ?: function (Operation $operation) { return $this->resumeOperation( $operation->getName(), - ['lastProtoResponse' => $operation] + $this->handleResponse($operation) ); }, function (array $args) use ($call) { @@ -117,4 +119,13 @@ function ($args) use ($call) { ) ); } + + private function operationFromOperationResponse( + OperationResponse $operation + ): LongRunningOperation { + return $this->resumeOperation( + $operation->getName(), + $this->handleResponse($operation->getLastProtoResponse()) ?? [] + ); + } } diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index 600d1b82a554..4f6b66942131 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -20,7 +20,7 @@ use Google\ApiCore\ClientOptionsTrait; use Google\ApiCore\CredentialsWrapper; use Google\ApiCore\Middleware\MiddlewareInterface; -use Google\ApiCore\OperationResponse; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\ApiCore\ValidationException; use Google\Auth\FetchAuthTokenInterface; use Google\Cloud\Core\ClientTrait; @@ -376,7 +376,7 @@ public function batch($instanceId, $databaseId, array $options = []) * @type bool $validateOnly An option to validate, but not actually execute, the request, and provide the same * response. **Defaults to** `false`. * } - * @return OperationResponse + * @return LongRunningOperation * @throws ValidationException */ public function createInstanceConfiguration(InstanceConfiguration $baseConfig, $name, array $replicas, array $options = []) @@ -485,7 +485,7 @@ public function instanceConfiguration($name, array $options = []) * been generated by a previous call to the API. * } * - * @return ItemIterator<OperationResponse> + * @return ItemIterator<LongRunningOperation> */ public function instanceConfigOperations(array $options = []) { @@ -498,13 +498,18 @@ public function instanceConfigOperations(array $options = []) $request, $callOptions + ['resource-prefix' => $this->projectName], function (Operation $operation) { - return $this->resumeOperation( + return new LongRunningOperation( + new LongRunningGapicConnection($this->databaseAdminClient, $this->serializer), $operation->getName(), - ['lastProtoResponse' => $operation] - )->withResultFunction(function (InstanceConfig $result) { - $config = $this->serializer->encodeMessage($result); - return $this->instanceConfiguration($config['name'], $config); - }); + [ + 'type.googleapis.com/google.spanner.admin.instance.v1.ListInstanceConfigMetadata' => + fn (InstanceConfig $config) => $this->instanceConfiguration( + $config->getName(), + $this->handleResponse($config) + ), + ], + $this->handleResponse($operation) + ); }, ); } @@ -530,7 +535,7 @@ function (Operation $operation) { * @type array $labels For more information, see * [Using labels to organize Google Cloud Platform resources](https://cloudplatform.googleblog.com/2015/10/using-labels-to-organize-Google-Cloud-Platform-resources.html). * } - * @return OperationResponse + * @return LongRunningOperation * @codingStandardsIgnoreEnd */ public function createInstance(InstanceConfiguration $config, $name, array $options = []) @@ -653,26 +658,6 @@ public function connect($instance, $name, array $options = []) return $database; } - /** - * Resume a Long Running Operation - * - * Example: - * ``` - * $operation = $spanner->resumeOperation($operationName); - * ``` - * - * @param string $operationName The Long Running Operation name. - * @return OperationResponse - */ - public function resumeOperation($operationName, array $options = []) - { - return new OperationResponse( - $operationName, - $this->databaseAdminClient->getOperationsClient(), - $options - ); - } - /** * Create a new KeySet object * diff --git a/Spanner/tests/Snippet/BackupTest.php b/Spanner/tests/Snippet/BackupTest.php index 75ddc2077bc3..55c38da7d7e9 100644 --- a/Spanner/tests/Snippet/BackupTest.php +++ b/Spanner/tests/Snippet/BackupTest.php @@ -23,6 +23,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest; @@ -74,8 +75,6 @@ public function setUp(): void $this->serializer = new Serializer(); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); $this->expireTime = new \DateTime('+ 7 hours'); $database = $this->prophesize(Database::class); @@ -118,7 +117,7 @@ public function testCreate() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testCreateCopy() @@ -147,7 +146,7 @@ public function testCreateCopy() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testDelete() @@ -274,13 +273,12 @@ public function testUpdateExpireTime() public function testResumeOperation() { $snippet = $this->snippetFromMagicMethod(Backup::class, 'resumeOperation'); - $snippet->addLocal('spanner', new SpannerClient(['projectId' => 'my-project'])); $snippet->addLocal('backup', $this->backup); $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - $this->assertEquals('foo', $res->returnVal()->getName()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()->name()); } public function testLongRunningOperations() @@ -312,6 +310,6 @@ public function testLongRunningOperations() $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); } } diff --git a/Spanner/tests/Snippet/DatabaseTest.php b/Spanner/tests/Snippet/DatabaseTest.php index cb96afa60032..48c6a3b716bb 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -24,6 +24,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest; @@ -99,8 +100,6 @@ public function setUp(): void $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); $this->serializer = new Serializer(); $session = $this->prophesize(Session::class); @@ -230,7 +229,7 @@ public function testCreateBackup() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testName() @@ -316,7 +315,7 @@ public function testCreate() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } /** @@ -337,7 +336,7 @@ public function testRestore() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } /** @@ -989,8 +988,8 @@ public function testResumeOperation() $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - $this->assertEquals('foo', $res->returnVal()->getName()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()->name()); } public function testLongRunningOperations() @@ -1013,11 +1012,11 @@ public function testLongRunningOperations() ->willReturn($pagedListResponse->reveal()); $this->databaseAdminClient->getOperationsClient() - ->shouldBeCalledTimes(2) + ->shouldBeCalledOnce() ->willReturn($operationsClient->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); } } diff --git a/Spanner/tests/Snippet/InstanceConfigurationTest.php b/Spanner/tests/Snippet/InstanceConfigurationTest.php index a374be400720..ecebc9996944 100644 --- a/Spanner/tests/Snippet/InstanceConfigurationTest.php +++ b/Spanner/tests/Snippet/InstanceConfigurationTest.php @@ -20,6 +20,7 @@ use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; @@ -55,8 +56,6 @@ public function setUp(): void $this->serializer = new Serializer(); $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); $this->config = new InstanceConfiguration( $this->instanceAdminClient->reveal(), @@ -103,7 +102,7 @@ public function testCreate() $snippet->addLocal('instanceConfig', $this->config); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testUpdate() @@ -116,7 +115,7 @@ public function testUpdate() Argument::type('array') ) ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationResponse::class)->reveal()); + ->willReturn($this->operationResponse->reveal()); $snippet->invoke(); } diff --git a/Spanner/tests/Snippet/InstanceTest.php b/Spanner/tests/Snippet/InstanceTest.php index 63ca597a02aa..0a1f74e23bc1 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -24,6 +24,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest; @@ -87,8 +88,6 @@ public function setUp(): void $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); $this->page = $this->prophesize(Page::class); $this->page->getNextPageToken() @@ -140,7 +139,7 @@ public function testCreate() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testName() @@ -260,7 +259,7 @@ public function testCreateDatabase() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testCreateDatabaseFromBackup() @@ -278,7 +277,7 @@ public function testCreateDatabaseFromBackup() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } public function testDatabase() @@ -380,14 +379,11 @@ public function testBackupOperations() ) ->shouldBeCalledOnce() ->willReturn($this->pagedListResponse->reveal()); - $this->databaseAdminClient->getOperationsClient() - ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationsClient::class)->reveal()); $res = $snippet->invoke('backupOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()->current()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()->current()); } public function testDatabaseOperations() @@ -411,14 +407,11 @@ public function testDatabaseOperations() ) ->shouldBeCalledOnce() ->willReturn($this->pagedListResponse->reveal()); - $this->databaseAdminClient->getOperationsClient() - ->shouldBeCalledOnce() - ->willReturn($this->prophesize(OperationsClient::class)->reveal()); $res = $snippet->invoke('databaseOperations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()->current()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()->current()); } public function testIam() @@ -437,8 +430,8 @@ public function testResumeOperation() $snippet->addLocal('operationName', 'foo'); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - $this->assertEquals('foo', $res->returnVal()->getName()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()->name()); } public function testLongRunningOperations() @@ -461,12 +454,12 @@ public function testLongRunningOperations() ->willReturn($pagedListResponse->reveal()); $this->instanceAdminClient->getOperationsClient() - ->shouldBeCalledTimes(2) + ->shouldBeCalledOnce() ->willReturn($operationsClient->reveal()); $res = $snippet->invoke('operations'); $this->assertInstanceOf(ItemIterator::class, $res->returnVal()); - $this->assertContainsOnlyInstancesOf(OperationResponse::class, $res->returnVal()); + $this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal()); } public function testDatabaseWithDatabaseRole() diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index 454c377427cb..fa3cf903a003 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -24,6 +24,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\SnippetTestCase; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\CreateInstanceRequest; use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; @@ -89,8 +90,6 @@ public function setUp(): void 'gapicSpannerInstanceAdminClient' => $this->instanceAdminClient->reveal(), ]); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); } public function testClass() @@ -172,7 +171,7 @@ public function testCreateInstance() ->willReturn($this->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); + $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); } /** @@ -297,16 +296,6 @@ public function factoriesProvider() ]; } - public function testResumeOperation() - { - $snippet = $this->snippetFromMagicMethod(SpannerClient::class, 'resumeOperation'); - $snippet->addLocal('spanner', $this->client); - $snippet->addLocal('operationName', 'operations/foo'); - - $res = $snippet->invoke('operation'); - $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); - } - public function testEmulator() { $snippet = $this->snippetFromClass(SpannerClient::class, 1); diff --git a/Spanner/tests/Unit/BackupTest.php b/Spanner/tests/Unit/BackupTest.php index 20beb3320b6b..94b502c24bab 100644 --- a/Spanner/tests/Unit/BackupTest.php +++ b/Spanner/tests/Unit/BackupTest.php @@ -21,6 +21,7 @@ use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Google\ApiCore\OperationResponse; use Google\Cloud\Core\ApiHelperTrait; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; @@ -33,6 +34,7 @@ use Google\Cloud\Spanner\Database; use Google\Cloud\Spanner\Instance; use Google\Cloud\Spanner\Serializer; +use Google\LongRunning\Client\OperationsClient; use Google\Protobuf\FieldMask; use Google\Protobuf\Timestamp; use PHPUnit\Framework\TestCase; @@ -70,6 +72,8 @@ public function setUp(): void $this->checkAndSkipGrpcTests(); $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->databaseAdminClient->getOperationsClient() + ->willReturn($this->prophesize(OperationsClient::class)->reveal()); $this->serializer = new Serializer(); $this->database = $this->prophesize(Database::class); @@ -82,8 +86,6 @@ public function setUp(): void $this->instance->database(Argument::any())->willReturn($this->database); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); $this->expireTime = new DateTime('+7 hours'); $this->versionTime = new DateTime('-2 hours'); @@ -138,7 +140,7 @@ public function testCreate() $operation = $backup->create(self::DATABASE, $this->expireTime, [ 'versionTime' => $this->versionTime, ]); - $this->assertInstanceOf(OperationResponse::class, $operation); + $this->assertInstanceOf(LongRunningOperation::class, $operation); } public function testCreateCopy() @@ -178,7 +180,7 @@ public function testCreateCopy() ); $op = $backup->createCopy($copiedBackup, $this->expireTime); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } public function testDelete() diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index 99c4037f8d93..11063fcb8673 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -30,6 +30,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\Fixtures; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Backup; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; @@ -77,6 +78,7 @@ use Google\Protobuf\ListValue; use Google\Protobuf\Timestamp as TimestampProto; use Google\Protobuf\Value; +use Google\LongRunning\Client\OperationsClient; use Google\Rpc\Code; use Google\Rpc\Status; use PHPUnit\Framework\TestCase; @@ -148,6 +150,8 @@ public function setUp(): void $this->spannerClient = $this->prophesize(SpannerClient::class); $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + $this->databaseAdminClient->getOperationsClient() + ->willReturn($this->prophesize(OperationsClient::class)); $this->session = new Session( $this->spannerClient->reveal(), @@ -191,8 +195,6 @@ public function setUp(): void ); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); } public function testName() @@ -263,7 +265,7 @@ public function testCreateBackup() $op = $this->database->createBackup(self::BACKUP, $expireTime); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } public function testBackups() @@ -436,7 +438,7 @@ public function testCreate() ] ]); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } /** @@ -460,7 +462,7 @@ public function testUpdateDatabase() ->willReturn($this->operationResponse->reveal()); $op = $this->database->updateDatabase(['enableDropProtection' => true]); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } /** @@ -486,7 +488,7 @@ public function testCreatePostgresDialect() 'databaseDialect' => DatabaseDialect::POSTGRESQL ]); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } /** @@ -513,7 +515,7 @@ public function testRestoreFromBackupName() ->willReturn($this->operationResponse->reveal()); $op = $this->database->restore($backupName); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } /** @@ -540,7 +542,7 @@ public function testRestoreFromBackupObject() ->willReturn($this->operationResponse->reveal()); $op = $this->database->restore($backupObj); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } /** @@ -566,7 +568,7 @@ public function testUpdateDdl() $res = $this->database->updateDdl($statement); - $this->assertInstanceOf(OperationResponse::class, $res); + $this->assertInstanceOf(LongRunningOperation::class, $res); } /** * @group spanner-admin @@ -616,7 +618,7 @@ public function testUpdateWithSingleStatement() ->willReturn($this->operationResponse->reveal()); $res = $this->database->updateDdl($statement); - $this->assertInstanceOf(OperationResponse::class, $res); + $this->assertInstanceOf(LongRunningOperation::class, $res); } /** diff --git a/Spanner/tests/Unit/InstanceConfigurationTest.php b/Spanner/tests/Unit/InstanceConfigurationTest.php index 32c2d66d5d5a..ab1a95f99545 100644 --- a/Spanner/tests/Unit/InstanceConfigurationTest.php +++ b/Spanner/tests/Unit/InstanceConfigurationTest.php @@ -24,9 +24,12 @@ use Google\Cloud\Spanner\Admin\Instance\V1\DeleteInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\GetInstanceConfigRequest; use Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig; +use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigMetadata; use Google\Cloud\Spanner\Admin\Instance\V1\UpdateInstanceConfigRequest; use Google\Cloud\Spanner\InstanceConfiguration; use Google\Cloud\Spanner\Serializer; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\GetOperationRequest; use Google\LongRunning\Operation; use Google\Protobuf\Any; use Google\Rpc\Code; @@ -47,13 +50,17 @@ class InstanceConfigurationTest extends TestCase const NAME = 'test-config'; private $instanceAdminClient; + private $operationsClient; private Serializer $serializer; public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->operationsClient = $this->prophesize(OperationsClient::class); $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); + $this->instanceAdminClient->getOperationsClient() + ->willReturn($this->operationsClient->reveal()); $this->serializer = new Serializer(); } @@ -186,13 +193,23 @@ public function testUpdate() 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT_ID, 'foo'), 'display_name' => 'bar2' ]); - $any = new Any(['value' => $expectedInstanceConfig->serializeToString()]); - $operationProto = new Operation(['response' => $any, 'done' => true]); - $operationClient = $this->prophesize(\Google\LongRunning\Client\OperationsClient::class); - $operationResponse = new OperationResponse('operation-name', $operationClient->reveal(), [ + $result = new Any(); + $result->pack($expectedInstanceConfig); + $metadata = new Any(); + $metadata->pack(new UpdateInstanceConfigMetadata()); + $operationProto = new Operation([ + 'response' => $result, + 'metadata' => $metadata, + 'done' => true + ]); + + $operationResponse = new OperationResponse('operation-name', $this->operationsClient->reveal(), [ 'operationReturnType' => InstanceConfig::class, 'lastProtoResponse' => $operationProto, ]); + $this->instanceAdminClient->resumeOperation($operationResponse->getName()) + ->shouldBeCalledOnce() + ->willReturn($operationResponse); $this->instanceAdminClient->updateInstanceConfig( Argument::that(function (UpdateInstanceConfigRequest $request) use ($expectedInstanceConfig) { @@ -214,7 +231,7 @@ public function testUpdate() $operation = $instanceConfig->update(['displayName' => 'bar2']); $operation->pollUntilComplete(); - $updatedInstanceConfig = $operation->getResult(); + $updatedInstanceConfig = $operation->result(); $info = $updatedInstanceConfig->info(); $this->assertEquals('bar2', $info['displayName']); diff --git a/Spanner/tests/Unit/InstanceTest.php b/Spanner/tests/Unit/InstanceTest.php index b4863381d668..92812e39316a 100644 --- a/Spanner/tests/Unit/InstanceTest.php +++ b/Spanner/tests/Unit/InstanceTest.php @@ -24,6 +24,7 @@ use Google\Cloud\Core\Iam\IamManager; use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Backup as BackupProto; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\Database as DatabaseProto; @@ -96,9 +97,6 @@ public function setUp(): void $this->instanceAdminClient = $this->prophesize(InstanceAdminClient::class); $this->databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); - $this->page = $this->prophesize(Page::class); $this->pagedListResponse = $this->prophesize(PagedListResponse::class); $this->pagedListResponse->getPage()->willReturn($this->page->reveal()); @@ -404,7 +402,7 @@ public function testCreateDatabase() 'statements' => $extra ]); - $this->assertInstanceOf(OperationResponse::class, $database); + $this->assertInstanceOf(LongRunningOperation::class, $database); } public function testCreateDatabaseFromBackupName() @@ -424,7 +422,7 @@ public function testCreateDatabaseFromBackupName() ->willReturn($this->operationResponse->reveal()); $op = $this->instance->createDatabaseFromBackup('restore-database', $backupName); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } public function testCreateDatabaseFromBackupObject() @@ -444,7 +442,7 @@ public function testCreateDatabaseFromBackupObject() ->willReturn($this->operationResponse->reveal()); $op = $this->instance->createDatabaseFromBackup('restore-database', $backupObject); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); } public function testDatabase() @@ -622,18 +620,14 @@ public function testBackupOperations() ->shouldBeCalledOnce() ->willReturn($this->pagedListResponse->reveal()); - $this->databaseAdminClient->getOperationsClient() - ->shouldBeCalledTimes(2) - ->willReturn($this->prophesize(OperationsClient::class)->reveal()); - $bkpOps = $this->instance->backupOperations(); $this->assertInstanceOf(ItemIterator::class, $bkpOps); $bkpOps = iterator_to_array($bkpOps); $this->assertCount(2, $bkpOps); - $this->assertEquals('operation1', $bkpOps[0]->getName()); - $this->assertEquals('operation2', $bkpOps[1]->getName()); + $this->assertEquals('operation1', $bkpOps[0]->name()); + $this->assertEquals('operation2', $bkpOps[1]->name()); } public function testListDatabaseOperations() @@ -656,18 +650,14 @@ public function testListDatabaseOperations() ->shouldBeCalledOnce() ->willReturn($this->pagedListResponse->reveal()); - $this->databaseAdminClient->getOperationsClient() - ->shouldBeCalledTimes(2) - ->willReturn($this->prophesize(OperationsClient::class)->reveal()); - $dbOps = $this->instance->databaseOperations(); $this->assertInstanceOf(ItemIterator::class, $dbOps); $dbOps = iterator_to_array($dbOps); $this->assertCount(2, $dbOps); - $this->assertEquals('operation1', $dbOps[0]->getName()); - $this->assertEquals('operation2', $dbOps[1]->getName()); + $this->assertEquals('operation1', $dbOps[0]->name()); + $this->assertEquals('operation2', $dbOps[1]->name()); } public function testInstanceDatabaseRole() diff --git a/Spanner/tests/Unit/SpannerClientTest.php b/Spanner/tests/Unit/SpannerClientTest.php index 06aaa1073696..62c41e68fabf 100644 --- a/Spanner/tests/Unit/SpannerClientTest.php +++ b/Spanner/tests/Unit/SpannerClientTest.php @@ -24,6 +24,7 @@ use Google\Cloud\Core\Iterator\ItemIterator; use Google\Cloud\Core\Testing\GrpcTestTrait; use Google\Cloud\Core\Testing\Snippet\Fixtures; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\Instance as InstanceProto; @@ -97,8 +98,6 @@ public function setUp(): void ]); $this->operationResponse = $this->prophesize(OperationResponse::class); - $this->operationResponse->withResultFunction(Argument::type('callable')) - ->willReturn($this->operationResponse->reveal()); } public function testBatch() @@ -269,7 +268,7 @@ public function testCreateInstance() $operation = $this->spannerClient->createInstance($config->reveal(), self::INSTANCE); - $this->assertInstanceOf(OperationResponse::class, $operation); + $this->assertInstanceOf(LongRunningOperation::class, $operation); } /** @@ -305,7 +304,7 @@ public function testCreateInstanceWithNodes() 'nodeCount' => 2 ]); - $this->assertInstanceOf(OperationResponse::class, $operation); + $this->assertInstanceOf(LongRunningOperation::class, $operation); } /** @@ -344,7 +343,7 @@ public function testCreateInstanceWithProcessingUnits() 'processingUnits' => 2000 ]); - $this->assertInstanceOf(OperationResponse::class, $operation); + $this->assertInstanceOf(LongRunningOperation::class, $operation); } /** @@ -419,18 +418,6 @@ public function testInstances() $this->assertEquals('bar', InstanceAdminClient::parseName($instances[1]->name())['instance']); } - /** - * @group spanner-admin - */ - public function testResumeOperation() - { - $opName = 'operations/foo'; - - $op = $this->spannerClient->resumeOperation($opName); - $this->assertInstanceOf(OperationResponse::class, $op); - $this->assertEquals($op->getName(), $opName); - } - public function testConnect() { $database = $this->spannerClient->connect(self::INSTANCE, self::DATABASE); From 4583ac1c30e6c28a8757e9023dd6dcc39064df34 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Tue, 26 Nov 2024 09:56:28 -0800 Subject: [PATCH 10/12] cs fix --- Spanner/src/Database.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 26bbad6dbc75..b07637cd89df 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -17,7 +17,8 @@ namespace Google\Cloud\Spanner; -use Closure;use Google\ApiCore\ApiException; +use Closure; +use Google\ApiCore\ApiException; use Google\ApiCore\RetrySettings; use Google\ApiCore\ValidationException; From c53edc39f6d98316b17394ea922603500681a71c Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Wed, 27 Nov 2024 14:27:45 +0000 Subject: [PATCH 11/12] fix system test --- .../LongRunning/LongRunningGapicConnection.php | 10 ++++++++-- Spanner/tests/System/AdminTest.php | 16 ++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Core/src/LongRunning/LongRunningGapicConnection.php b/Core/src/LongRunning/LongRunningGapicConnection.php index 1b24eb83da08..61c82d235d73 100644 --- a/Core/src/LongRunning/LongRunningGapicConnection.php +++ b/Core/src/LongRunning/LongRunningGapicConnection.php @@ -19,12 +19,13 @@ use Google\ApiCore\OperationResponse; use Google\ApiCore\Serializer; +use Google\Cloud\Core\RequestProcessorTrait; use Google\LongRunning\Operation; use Google\LongRunning\ListOperationsRequest; use Google\LongRunning\GetOperationRequest; use Google\LongRunning\CancelOperationRequest; use Google\LongRunning\DeleteOperationRequest; -use Google\Cloud\Core\RequestProcessorTrait; +use Google\Protobuf\Any; /** * Defines the calls required to manage Long Running Operations using a GAPIC @@ -97,7 +98,12 @@ private function operationResponseToArray(OperationResponse $operationResponse) $metaType = $response['metadata']['typeUrl']; // unpack result Any type - $response['response'] = $this->handleResponse($operationResponse->getResult()); + $result = $operationResponse->getResult(); + if ($result instanceof Any) { + // For some reason we aren't doing this in GAX OperationResponse (but we should) + $result = $result->unpack(); + } + $response['response'] = $this->handleResponse($result); // unpack error Any type $response['error'] = $this->handleResponse($operationResponse->getError()); diff --git a/Spanner/tests/System/AdminTest.php b/Spanner/tests/System/AdminTest.php index b8c5ffee6f2b..269649649ffd 100644 --- a/Spanner/tests/System/AdminTest.php +++ b/Spanner/tests/System/AdminTest.php @@ -17,8 +17,8 @@ namespace Google\Cloud\Spanner\Tests\System; -use Google\ApiCore\OperationResponse; use Google\Cloud\Core\Exception\FailedPreconditionException; +use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; @@ -62,7 +62,7 @@ public function testInstance() 'processingUnits' => $processingUnits, ]); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); $op->pollUntilComplete(); $instance = $client->instance(self::INSTANCE_NAME); @@ -97,9 +97,9 @@ public function testDatabase() $dbName = uniqid(self::TESTING_PREFIX); $op = $instance->createDatabase($dbName); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); $op->pollUntilComplete(); - $db = $op->getResult(); + $db = $op->result(); $this->assertInstanceOf(Database::class, $db); self::$deletionQueue->add(function () use ($db) { @@ -141,9 +141,9 @@ public function testDatabaseDropProtection() $dbName = uniqid(self::TESTING_PREFIX); $op = $instance->createDatabase($dbName); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); $op->pollUntilComplete(); - $db = $op->getResult(); + $db = $op->result(); $this->assertInstanceOf(Database::class, $db); $info = $db->reload(); @@ -207,7 +207,7 @@ public function testCreateCustomerManagedInstanceConfiguration() $replicas[array_rand($replicas)]['defaultLeaderLocation'] = true; $op = $customConfiguration->create($baseConfig, $replicas); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); $op->pollUntilComplete(); $this->assertTrue($customConfiguration->exists()); @@ -293,7 +293,7 @@ public function testPgDatabase() 'databaseDialect' => DatabaseDialect::POSTGRESQL ]); - $this->assertInstanceOf(OperationResponse::class, $op); + $this->assertInstanceOf(LongRunningOperation::class, $op); $db = $op->pollUntilComplete(); $this->assertInstanceOf(Database::class, $db); From 7d3edb7bf8cea86a370a9118987fb258b4d69c46 Mon Sep 17 00:00:00 2001 From: Brent Shaffer <betterbrent@google.com> Date: Wed, 27 Nov 2024 06:52:53 -0800 Subject: [PATCH 12/12] fix phpstan --- Spanner/src/SpannerClient.php | 65 +++++++++++++++++++++ Spanner/tests/Snippet/SpannerClientTest.php | 13 +++++ Spanner/tests/Unit/SpannerClientTest.php | 12 ++++ 3 files changed, 90 insertions(+) diff --git a/Spanner/src/SpannerClient.php b/Spanner/src/SpannerClient.php index 4f6b66942131..74845af7723b 100644 --- a/Spanner/src/SpannerClient.php +++ b/Spanner/src/SpannerClient.php @@ -28,6 +28,7 @@ use Google\Cloud\Core\Exception\GoogleException; use Google\Cloud\Core\Int64; use Google\Cloud\Core\Iterator\ItemIterator; +use Google\Cloud\Core\LongRunning\LongRunningGapicConnection; use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient; use Google\Cloud\Spanner\Admin\Instance\V1\ListInstanceConfigOperationsRequest; @@ -514,6 +515,70 @@ function (Operation $operation) { ); } + /** + * Resume a Long Running Operation + * + * Example: + * ``` + * $operation = $spanner->resumeOperation($operationName); + * ``` + * + * @param string $operationName The Long Running Operation name. + * @return LongRunningOperation + */ + public function resumeOperation($operationName, array $info = []) + { + return new LongRunningOperation( + new LongRunningGapicConnection($this->databaseAdminClient, $this->serializer), + $operationName, + [ + [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.UpdateInstanceMetadata', + 'callable' => function ($instance) { + $name = InstanceAdminClient::parseName($instance['name'])['instance']; + return $this->instance($name, $instance); + } + ], [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateDatabaseMetadata', + 'callable' => function ($database) { + $databaseNameComponents = DatabaseAdminClient::parseName($database['name']); + $instanceName = $databaseNameComponents['instance']; + $databaseName = $databaseNameComponents['database']; + + $instance = $this->instance($instanceName); + return $instance->database($databaseName); + } + ], [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata', + 'callable' => function ($database) { + $databaseNameComponents = DatabaseAdminClient::parseName($database['name']); + $instanceName = $databaseNameComponents['instance']; + $databaseName = $databaseNameComponents['database']; + + $instance = $this->instance($instanceName); + return $instance->database($databaseName); + } + ],[ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceMetadata', + 'callable' => function ($instance) { + $name = InstanceAdminClient::parseName($instance['name'])['instance']; + return $this->instance($name, $instance); + } + ], [ + 'typeUrl' => 'type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata', + 'callable' => function ($backup) { + $backupNameComponents = DatabaseAdminClient::parseName($backup['name']); + $instanceName = $backupNameComponents['instance']; + + $instance = $this->instance($instanceName); + return $instance->backup($backup['name'], $backup); + } + ] + ], + $info + ); + } + /** * Create a new instance. * diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index fa3cf903a003..c3cae42be789 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -296,6 +296,19 @@ public function factoriesProvider() ]; } + public function testResumeOperation() + { + $opName = 'operations/foo'; + $snippet = $this->snippetFromMagicMethod(SpannerClient::class, 'resumeOperation'); + $snippet->addLocal('spanner', $this->client); + $snippet->addLocal('operationName', $opName); + + $res = $snippet->invoke('operation'); + $op = $res->returnVal(); + $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertEquals($op->name(), $opName); + } + public function testEmulator() { $snippet = $this->snippetFromClass(SpannerClient::class, 1); diff --git a/Spanner/tests/Unit/SpannerClientTest.php b/Spanner/tests/Unit/SpannerClientTest.php index 62c41e68fabf..87a776743bef 100644 --- a/Spanner/tests/Unit/SpannerClientTest.php +++ b/Spanner/tests/Unit/SpannerClientTest.php @@ -418,6 +418,18 @@ public function testInstances() $this->assertEquals('bar', InstanceAdminClient::parseName($instances[1]->name())['instance']); } + /** + * @group spanner-admin + */ + public function testResumeOperation() + { + $opName = 'operations/foo'; + + $op = $this->spannerClient->resumeOperation($opName); + $this->assertInstanceOf(LongRunningOperation::class, $op); + $this->assertEquals($op->name(), $opName); + } + public function testConnect() { $database = $this->spannerClient->connect(self::INSTANCE, self::DATABASE);