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 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..0afffeb5bc3d --- /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/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/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/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..d47b162540d5 100644 --- a/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php +++ b/Core/tests/Unit/LongRunning/OperationResponseTraitTest.php @@ -18,10 +18,10 @@ 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 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/Admin/Database/V1/Gapic/DatabaseAdminGapicClient.php b/Spanner/src/Admin/Database/V1/Gapic/DatabaseAdminGapicClient.php deleted file mode 100644 index 76651160c907..000000000000 --- a/Spanner/src/Admin/Database/V1/Gapic/DatabaseAdminGapicClient.php +++ /dev/null @@ -1,2700 +0,0 @@ -instanceName('[PROJECT]', '[INSTANCE]'); - * $backupId = 'backup_id'; - * $formattedSourceBackup = $databaseAdminClient->backupName('[PROJECT]', '[INSTANCE]', '[BACKUP]'); - * $expireTime = new Timestamp(); - * $operationResponse = $databaseAdminClient->copyBackup($formattedParent, $backupId, $formattedSourceBackup, $expireTime); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->copyBackup($formattedParent, $backupId, $formattedSourceBackup, $expireTime); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'copyBackup'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * Many parameters require resource names to be formatted in a particular way. To - * assist with these names, this class includes a format method for each type of - * name, and additionally a parseName method to extract the individual identifiers - * contained within formatted names that are returned by the API. - * - * @deprecated Please use the new service client {@see \Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient}. - */ -class DatabaseAdminGapicClient -{ - use GapicClientTrait; - - /** The name of the service. */ - const SERVICE_NAME = 'google.spanner.admin.database.v1.DatabaseAdmin'; - - /** - * The default address of the service. - * - * @deprecated SERVICE_ADDRESS_TEMPLATE should be used instead. - */ - const SERVICE_ADDRESS = 'spanner.googleapis.com'; - - /** The address template of the service. */ - private const SERVICE_ADDRESS_TEMPLATE = 'spanner.UNIVERSE_DOMAIN'; - - /** The default port of the service. */ - const DEFAULT_SERVICE_PORT = 443; - - /** The name of the code generator, to be included in the agent header. */ - const CODEGEN_NAME = 'gapic'; - - /** The default scopes required by the service. */ - public static $serviceScopes = [ - 'https://www.googleapis.com/auth/cloud-platform', - 'https://www.googleapis.com/auth/spanner.admin', - ]; - - private static $backupNameTemplate; - - private static $backupScheduleNameTemplate; - - private static $cryptoKeyNameTemplate; - - private static $cryptoKeyVersionNameTemplate; - - private static $databaseNameTemplate; - - private static $instanceNameTemplate; - - private static $pathTemplateMap; - - private $operationsClient; - - private static function getClientDefaults() - { - return [ - 'serviceName' => self::SERVICE_NAME, - 'apiEndpoint' => - self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, - 'clientConfig' => - __DIR__ . '/../resources/database_admin_client_config.json', - 'descriptorsConfigPath' => - __DIR__ . '/../resources/database_admin_descriptor_config.php', - 'gcpApiConfigPath' => - __DIR__ . '/../resources/database_admin_grpc_config.json', - 'credentialsConfig' => [ - 'defaultScopes' => self::$serviceScopes, - ], - 'transportConfig' => [ - 'rest' => [ - 'restClientConfigPath' => - __DIR__ . - '/../resources/database_admin_rest_client_config.php', - ], - ], - ]; - } - - private static function getBackupNameTemplate() - { - if (self::$backupNameTemplate == null) { - self::$backupNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/backups/{backup}' - ); - } - - return self::$backupNameTemplate; - } - - private static function getBackupScheduleNameTemplate() - { - if (self::$backupScheduleNameTemplate == null) { - self::$backupScheduleNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/databases/{database}/backupSchedules/{schedule}' - ); - } - - return self::$backupScheduleNameTemplate; - } - - private static function getCryptoKeyNameTemplate() - { - if (self::$cryptoKeyNameTemplate == null) { - self::$cryptoKeyNameTemplate = new PathTemplate( - 'projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}' - ); - } - - return self::$cryptoKeyNameTemplate; - } - - private static function getCryptoKeyVersionNameTemplate() - { - if (self::$cryptoKeyVersionNameTemplate == null) { - self::$cryptoKeyVersionNameTemplate = new PathTemplate( - 'projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}/cryptoKeyVersions/{crypto_key_version}' - ); - } - - return self::$cryptoKeyVersionNameTemplate; - } - - private static function getDatabaseNameTemplate() - { - if (self::$databaseNameTemplate == null) { - self::$databaseNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/databases/{database}' - ); - } - - return self::$databaseNameTemplate; - } - - private static function getInstanceNameTemplate() - { - if (self::$instanceNameTemplate == null) { - self::$instanceNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}' - ); - } - - return self::$instanceNameTemplate; - } - - private static function getPathTemplateMap() - { - if (self::$pathTemplateMap == null) { - self::$pathTemplateMap = [ - 'backup' => self::getBackupNameTemplate(), - 'backupSchedule' => self::getBackupScheduleNameTemplate(), - 'cryptoKey' => self::getCryptoKeyNameTemplate(), - 'cryptoKeyVersion' => self::getCryptoKeyVersionNameTemplate(), - 'database' => self::getDatabaseNameTemplate(), - 'instance' => self::getInstanceNameTemplate(), - ]; - } - - return self::$pathTemplateMap; - } - - /** - * Formats a string containing the fully-qualified path to represent a backup - * resource. - * - * @param string $project - * @param string $instance - * @param string $backup - * - * @return string The formatted backup resource. - */ - public static function backupName($project, $instance, $backup) - { - return self::getBackupNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'backup' => $backup, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a - * backup_schedule resource. - * - * @param string $project - * @param string $instance - * @param string $database - * @param string $schedule - * - * @return string The formatted backup_schedule resource. - */ - public static function backupScheduleName( - $project, - $instance, - $database, - $schedule - ) { - return self::getBackupScheduleNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'database' => $database, - 'schedule' => $schedule, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a crypto_key - * resource. - * - * @param string $project - * @param string $location - * @param string $keyRing - * @param string $cryptoKey - * - * @return string The formatted crypto_key resource. - */ - public static function cryptoKeyName( - $project, - $location, - $keyRing, - $cryptoKey - ) { - return self::getCryptoKeyNameTemplate()->render([ - 'project' => $project, - 'location' => $location, - 'key_ring' => $keyRing, - 'crypto_key' => $cryptoKey, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a - * crypto_key_version resource. - * - * @param string $project - * @param string $location - * @param string $keyRing - * @param string $cryptoKey - * @param string $cryptoKeyVersion - * - * @return string The formatted crypto_key_version resource. - */ - public static function cryptoKeyVersionName( - $project, - $location, - $keyRing, - $cryptoKey, - $cryptoKeyVersion - ) { - return self::getCryptoKeyVersionNameTemplate()->render([ - 'project' => $project, - 'location' => $location, - 'key_ring' => $keyRing, - 'crypto_key' => $cryptoKey, - 'crypto_key_version' => $cryptoKeyVersion, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a database - * resource. - * - * @param string $project - * @param string $instance - * @param string $database - * - * @return string The formatted database resource. - */ - public static function databaseName($project, $instance, $database) - { - return self::getDatabaseNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'database' => $database, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a instance - * resource. - * - * @param string $project - * @param string $instance - * - * @return string The formatted instance resource. - */ - public static function instanceName($project, $instance) - { - return self::getInstanceNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - ]); - } - - /** - * Parses a formatted name string and returns an associative array of the components in the name. - * The following name formats are supported: - * Template: Pattern - * - backup: projects/{project}/instances/{instance}/backups/{backup} - * - backupSchedule: projects/{project}/instances/{instance}/databases/{database}/backupSchedules/{schedule} - * - cryptoKey: projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key} - * - cryptoKeyVersion: projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}/cryptoKeyVersions/{crypto_key_version} - * - database: projects/{project}/instances/{instance}/databases/{database} - * - instance: projects/{project}/instances/{instance} - * - * The optional $template argument can be supplied to specify a particular pattern, - * and must match one of the templates listed above. If no $template argument is - * provided, or if the $template argument does not match one of the templates - * listed, then parseName will check each of the supported templates, and return - * the first match. - * - * @param string $formattedName The formatted name string - * @param string $template Optional name of template to match - * - * @return array An associative array from name component IDs to component values. - * - * @throws ValidationException If $formattedName could not be matched. - */ - public static function parseName($formattedName, $template = null) - { - $templateMap = self::getPathTemplateMap(); - if ($template) { - if (!isset($templateMap[$template])) { - throw new ValidationException( - "Template name $template does not exist" - ); - } - - return $templateMap[$template]->match($formattedName); - } - - foreach ($templateMap as $templateName => $pathTemplate) { - try { - return $pathTemplate->match($formattedName); - } catch (ValidationException $ex) { - // Swallow the exception to continue trying other path templates - } - } - - throw new ValidationException( - "Input did not match any known format. Input: $formattedName" - ); - } - - /** - * Return an OperationsClient object with the same endpoint as $this. - * - * @return OperationsClient - */ - public function getOperationsClient() - { - return $this->operationsClient; - } - - /** - * Resume an existing long running operation that was previously started by a long - * running API method. If $methodName is not provided, or does not match a long - * running API method, then the operation can still be resumed, but the - * OperationResponse object will not deserialize the final response. - * - * @param string $operationName The name of the long running operation - * @param string $methodName The name of the method used to start the operation - * - * @return OperationResponse - */ - public function resumeOperation($operationName, $methodName = null) - { - $options = isset($this->descriptors[$methodName]['longRunning']) - ? $this->descriptors[$methodName]['longRunning'] - : []; - $operation = new OperationResponse( - $operationName, - $this->getOperationsClient(), - $options - ); - $operation->reload(); - return $operation; - } - - /** - * Constructor. - * - * @param array $options { - * Optional. Options for configuring the service API wrapper. - * - * @type string $apiEndpoint - * The address of the API remote host. May optionally include the port, formatted - * as ":". Default 'spanner.googleapis.com:443'. - * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials - * The credentials to be used by the client to authorize API calls. This option - * accepts either a path to a credentials file, or a decoded credentials file as a - * PHP array. - * *Advanced usage*: In addition, this option can also accept a pre-constructed - * {@see \Google\Auth\FetchAuthTokenInterface} object or - * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these - * objects are provided, any settings in $credentialsConfig will be ignored. - * @type array $credentialsConfig - * Options used to configure credentials, including auth token caching, for the - * client. For a full list of supporting configuration options, see - * {@see \Google\ApiCore\CredentialsWrapper::build()} . - * @type bool $disableRetries - * Determines whether or not retries defined by the client configuration should be - * disabled. Defaults to `false`. - * @type string|array $clientConfig - * Client method configuration, including retry settings. This option can be either - * a path to a JSON file, or a PHP array containing the decoded JSON data. By - * default this settings points to the default client config file, which is - * provided in the resources folder. - * @type string|TransportInterface $transport - * The transport used for executing network requests. May be either the string - * `rest` or `grpc`. Defaults to `grpc` if gRPC support is detected on the system. - * *Advanced usage*: Additionally, it is possible to pass in an already - * instantiated {@see \Google\ApiCore\Transport\TransportInterface} object. Note - * that when this object is provided, any settings in $transportConfig, and any - * $apiEndpoint setting, will be ignored. - * @type array $transportConfig - * Configuration options that will be used to construct the transport. Options for - * each supported transport type should be passed in a key for that transport. For - * example: - * $transportConfig = [ - * 'grpc' => [...], - * 'rest' => [...], - * ]; - * See the {@see \Google\ApiCore\Transport\GrpcTransport::build()} and - * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the - * supported options. - * @type callable $clientCertSource - * A callable which returns the client cert as a string. This can be used to - * provide a certificate and private key to the transport layer for mTLS. - * } - * - * @throws ValidationException - */ - public function __construct(array $options = []) - { - $clientOptions = $this->buildClientOptions($options); - $this->setClientOptions($clientOptions); - $this->operationsClient = $this->createOperationsClient($clientOptions); - } - - /** - * Starts copying a Cloud Spanner Backup. - * The returned backup [long-running operation][google.longrunning.Operation] - * will have a name of the format - * `projects//instances//backups//operations/` - * and can be used to track copying of the backup. The operation is associated - * with the destination backup. - * The [metadata][google.longrunning.Operation.metadata] field type is - * [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Backup][google.spanner.admin.database.v1.Backup], if successful. - * Cancelling the returned operation will stop the copying and delete the - * destination backup. Concurrent CopyBackup requests can run on the same - * source backup. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $backupId = 'backup_id'; - * $formattedSourceBackup = $databaseAdminClient->backupName('[PROJECT]', '[INSTANCE]', '[BACKUP]'); - * $expireTime = new Timestamp(); - * $operationResponse = $databaseAdminClient->copyBackup($formattedParent, $backupId, $formattedSourceBackup, $expireTime); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->copyBackup($formattedParent, $backupId, $formattedSourceBackup, $expireTime); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'copyBackup'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the destination instance that will contain the backup - * copy. Values are of the form: `projects//instances/`. - * @param string $backupId Required. The id of the backup copy. - * The `backup_id` appended to `parent` forms the full backup_uri of the form - * `projects//instances//backups/`. - * @param string $sourceBackup Required. The source backup to be copied. - * The source backup needs to be in READY state for it to be copied. - * Once CopyBackup is in progress, the source backup cannot be deleted or - * cleaned up on expiration until CopyBackup is finished. - * Values are of the form: - * `projects//instances//backups/`. - * @param Timestamp $expireTime Required. The expiration time of the backup in microsecond granularity. - * The expiration time must be at least 6 hours and at most 366 days - * from the `create_time` of the source backup. Once the `expire_time` has - * passed, the backup is eligible to be automatically deleted by Cloud Spanner - * to free the resources used by the backup. - * @param array $optionalArgs { - * Optional. - * - * @type CopyBackupEncryptionConfig $encryptionConfig - * Optional. The encryption configuration used to encrypt the backup. If this - * field is not specified, the backup will use the same encryption - * configuration as the source backup by default, namely - * [encryption_type][google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type] - * = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function copyBackup( - $parent, - $backupId, - $sourceBackup, - $expireTime, - array $optionalArgs = [] - ) { - $request = new CopyBackupRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setBackupId($backupId); - $request->setSourceBackup($sourceBackup); - $request->setExpireTime($expireTime); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['encryptionConfig'])) { - $request->setEncryptionConfig($optionalArgs['encryptionConfig']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CopyBackup', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Starts creating a new Cloud Spanner Backup. - * The returned backup [long-running operation][google.longrunning.Operation] - * will have a name of the format - * `projects//instances//backups//operations/` - * and can be used to track creation of the backup. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Backup][google.spanner.admin.database.v1.Backup], if successful. - * Cancelling the returned operation will stop the creation and delete the - * backup. There can be only one pending backup creation per database. Backup - * creation of different databases can run concurrently. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $backupId = 'backup_id'; - * $backup = new Backup(); - * $operationResponse = $databaseAdminClient->createBackup($formattedParent, $backupId, $backup); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->createBackup($formattedParent, $backupId, $backup); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'createBackup'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the instance in which the backup will be - * created. This must be the same instance that contains the database the - * backup will be created from. The backup will be stored in the - * location(s) specified in the instance configuration of this - * instance. Values are of the form - * `projects//instances/`. - * @param string $backupId Required. The id of the backup to be created. The `backup_id` appended to - * `parent` forms the full backup name of the form - * `projects//instances//backups/`. - * @param Backup $backup Required. The backup to create. - * @param array $optionalArgs { - * Optional. - * - * @type CreateBackupEncryptionConfig $encryptionConfig - * Optional. The encryption configuration used to encrypt the backup. If this - * field is not specified, the backup will use the same encryption - * configuration as the database by default, namely - * [encryption_type][google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type] - * = `USE_DATABASE_ENCRYPTION`. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function createBackup( - $parent, - $backupId, - $backup, - array $optionalArgs = [] - ) { - $request = new CreateBackupRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setBackupId($backupId); - $request->setBackup($backup); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['encryptionConfig'])) { - $request->setEncryptionConfig($optionalArgs['encryptionConfig']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CreateBackup', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Creates a new backup schedule. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $backupScheduleId = 'backup_schedule_id'; - * $backupSchedule = new BackupSchedule(); - * $response = $databaseAdminClient->createBackupSchedule($formattedParent, $backupScheduleId, $backupSchedule); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the database that this backup schedule applies to. - * @param string $backupScheduleId Required. The Id to use for the backup schedule. The `backup_schedule_id` - * appended to `parent` forms the full backup schedule name of the form - * `projects//instances//databases//backupSchedules/`. - * @param BackupSchedule $backupSchedule Required. The backup schedule to create. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\BackupSchedule - * - * @throws ApiException if the remote call fails - */ - public function createBackupSchedule( - $parent, - $backupScheduleId, - $backupSchedule, - array $optionalArgs = [] - ) { - $request = new CreateBackupScheduleRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setBackupScheduleId($backupScheduleId); - $request->setBackupSchedule($backupSchedule); - $requestParamHeaders['parent'] = $parent; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'CreateBackupSchedule', - BackupSchedule::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Creates a new Cloud Spanner database and starts to prepare it for serving. - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format `/operations/` and - * can be used to track preparation of the database. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [CreateDatabaseMetadata][google.spanner.admin.database.v1.CreateDatabaseMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Database][google.spanner.admin.database.v1.Database], if successful. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $createStatement = 'create_statement'; - * $operationResponse = $databaseAdminClient->createDatabase($formattedParent, $createStatement); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->createDatabase($formattedParent, $createStatement); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'createDatabase'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the instance that will serve the new database. - * Values are of the form `projects//instances/`. - * @param string $createStatement Required. A `CREATE DATABASE` statement, which specifies the ID of the - * new database. The database ID must conform to the regular expression - * `[a-z][a-z0-9_\-]*[a-z0-9]` and be between 2 and 30 characters in length. - * If the database ID is a reserved word or if it contains a hyphen, the - * database ID must be enclosed in backticks (`` ` ``). - * @param array $optionalArgs { - * Optional. - * - * @type string[] $extraStatements - * Optional. A list of DDL statements to run inside the newly created - * database. Statements can create tables, indexes, etc. These - * statements execute atomically with the creation of the database: - * if there is an error in any statement, the database is not created. - * @type EncryptionConfig $encryptionConfig - * Optional. The encryption configuration for the database. If this field is - * not specified, Cloud Spanner will encrypt/decrypt all data at rest using - * Google default encryption. - * @type int $databaseDialect - * Optional. The dialect of the Cloud Spanner Database. - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect} - * @type string $protoDescriptors - * Optional. Proto descriptors used by CREATE/ALTER PROTO BUNDLE statements in - * 'extra_statements' above. - * Contains a protobuf-serialized - * [google.protobuf.FileDescriptorSet](https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto). - * To generate it, [install](https://grpc.io/docs/protoc-installation/) and - * run `protoc` with --include_imports and --descriptor_set_out. For example, - * to generate for moon/shot/app.proto, run - * ``` - * $protoc --proto_path=/app_path --proto_path=/lib_path \ - * --include_imports \ - * --descriptor_set_out=descriptors.data \ - * moon/shot/app.proto - * ``` - * For more details, see protobuffer [self - * description](https://developers.google.com/protocol-buffers/docs/techniques#self-description). - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function createDatabase( - $parent, - $createStatement, - array $optionalArgs = [] - ) { - $request = new CreateDatabaseRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setCreateStatement($createStatement); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['extraStatements'])) { - $request->setExtraStatements($optionalArgs['extraStatements']); - } - - if (isset($optionalArgs['encryptionConfig'])) { - $request->setEncryptionConfig($optionalArgs['encryptionConfig']); - } - - if (isset($optionalArgs['databaseDialect'])) { - $request->setDatabaseDialect($optionalArgs['databaseDialect']); - } - - if (isset($optionalArgs['protoDescriptors'])) { - $request->setProtoDescriptors($optionalArgs['protoDescriptors']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CreateDatabase', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Deletes a pending or completed - * [Backup][google.spanner.admin.database.v1.Backup]. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedName = $databaseAdminClient->backupName('[PROJECT]', '[INSTANCE]', '[BACKUP]'); - * $databaseAdminClient->deleteBackup($formattedName); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $name Required. Name of the backup to delete. - * Values are of the form - * `projects//instances//backups/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteBackup($name, array $optionalArgs = []) - { - $request = new DeleteBackupRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteBackup', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Deletes a backup schedule. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedName = $databaseAdminClient->backupScheduleName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SCHEDULE]'); - * $databaseAdminClient->deleteBackupSchedule($formattedName); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the schedule to delete. - * Values are of the form - * `projects//instances//databases//backupSchedules/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteBackupSchedule($name, array $optionalArgs = []) - { - $request = new DeleteBackupScheduleRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteBackupSchedule', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Drops (aka deletes) a Cloud Spanner database. - * Completed backups for the database will be retained according to their - * `expire_time`. - * Note: Cloud Spanner might continue to accept requests for a few seconds - * after the database has been deleted. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedDatabase = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $databaseAdminClient->dropDatabase($formattedDatabase); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $database Required. The database to be dropped. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function dropDatabase($database, array $optionalArgs = []) - { - $request = new DropDatabaseRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $requestParamHeaders['database'] = $database; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DropDatabase', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets metadata on a pending or completed - * [Backup][google.spanner.admin.database.v1.Backup]. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedName = $databaseAdminClient->backupName('[PROJECT]', '[INSTANCE]', '[BACKUP]'); - * $response = $databaseAdminClient->getBackup($formattedName); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $name Required. Name of the backup. - * Values are of the form - * `projects//instances//backups/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\Backup - * - * @throws ApiException if the remote call fails - */ - public function getBackup($name, array $optionalArgs = []) - { - $request = new GetBackupRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetBackup', - Backup::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets backup schedule for the input schedule name. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedName = $databaseAdminClient->backupScheduleName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SCHEDULE]'); - * $response = $databaseAdminClient->getBackupSchedule($formattedName); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the schedule to retrieve. - * Values are of the form - * `projects//instances//databases//backupSchedules/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\BackupSchedule - * - * @throws ApiException if the remote call fails - */ - public function getBackupSchedule($name, array $optionalArgs = []) - { - $request = new GetBackupScheduleRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetBackupSchedule', - BackupSchedule::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets the state of a Cloud Spanner database. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedName = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $response = $databaseAdminClient->getDatabase($formattedName); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the requested database. Values are of the form - * `projects//instances//databases/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\Database - * - * @throws ApiException if the remote call fails - */ - public function getDatabase($name, array $optionalArgs = []) - { - $request = new GetDatabaseRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetDatabase', - Database::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Returns the schema of a Cloud Spanner database as a list of formatted - * DDL statements. This method does not show pending schema updates, those may - * be queried using the [Operations][google.longrunning.Operations] API. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedDatabase = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $response = $databaseAdminClient->getDatabaseDdl($formattedDatabase); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $database Required. The database whose schema we wish to get. - * Values are of the form - * `projects//instances//databases/` - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlResponse - * - * @throws ApiException if the remote call fails - */ - public function getDatabaseDdl($database, array $optionalArgs = []) - { - $request = new GetDatabaseDdlRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $requestParamHeaders['database'] = $database; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetDatabaseDdl', - GetDatabaseDdlResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets the access control policy for a database or backup resource. - * Returns an empty policy if a database or backup exists but does not have a - * policy set. - * - * Authorization requires `spanner.databases.getIamPolicy` permission on - * [resource][google.iam.v1.GetIamPolicyRequest.resource]. - * For backups, authorization requires `spanner.backups.getIamPolicy` - * permission on [resource][google.iam.v1.GetIamPolicyRequest.resource]. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $resource = 'resource'; - * $response = $databaseAdminClient->getIamPolicy($resource); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy is being requested. - * See the operation documentation for the appropriate value for this field. - * @param array $optionalArgs { - * Optional. - * - * @type GetPolicyOptions $options - * OPTIONAL: A `GetPolicyOptions` object for specifying options to - * `GetIamPolicy`. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\Policy - * - * @throws ApiException if the remote call fails - */ - public function getIamPolicy($resource, array $optionalArgs = []) - { - $request = new GetIamPolicyRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $requestParamHeaders['resource'] = $resource; - if (isset($optionalArgs['options'])) { - $request->setOptions($optionalArgs['options']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetIamPolicy', - Policy::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Lists the backup [long-running operations][google.longrunning.Operation] in - * the given instance. A backup operation has a name of the form - * `projects//instances//backups//operations/`. - * The long-running operation - * [metadata][google.longrunning.Operation.metadata] field type - * `metadata.type_url` describes the type of the metadata. Operations returned - * include those that have completed/failed/canceled within the last 7 days, - * and pending operations. Operations returned are ordered by - * `operation.metadata.value.progress.start_time` in descending order starting - * from the most recently started operation. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listBackupOperations($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listBackupOperations($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The instance of the backup operations. Values are of - * the form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $filter - * An expression that filters the list of returned backup operations. - * - * A filter expression consists of a field name, a - * comparison operator, and a value for filtering. - * The value must be a string, a number, or a boolean. The comparison operator - * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. - * Colon `:` is the contains operator. Filter rules are not case sensitive. - * - * The following fields in the [operation][google.longrunning.Operation] - * are eligible for filtering: - * - * * `name` - The name of the long-running operation - * * `done` - False if the operation is in progress, else true. - * * `metadata.@type` - the type of metadata. For example, the type string - * for - * [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] - * is - * `type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata`. - * * `metadata.` - any field in metadata.value. - * `metadata.@type` must be specified first if filtering on metadata - * fields. - * * `error` - Error associated with the long-running operation. - * * `response.@type` - the type of response. - * * `response.` - any field in response.value. - * - * You can combine multiple expressions by enclosing each expression in - * parentheses. By default, expressions are combined with AND logic, but - * you can specify AND, OR, and NOT logic explicitly. - * - * Here are a few examples: - * - * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ - * `metadata.database:prod` - Returns operations where: - * * The operation's metadata type is - * [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. - * * The source database name of backup contains the string "prod". - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ - * `(metadata.name:howl) AND` \ - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ - * `(error:*)` - Returns operations where: - * * The operation's metadata type is - * [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. - * * The backup name contains the string "howl". - * * The operation started before 2018-03-28T14:50:00Z. - * * The operation resulted in an error. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) AND` \ - * `(metadata.source_backup:test) AND` \ - * `(metadata.progress.start_time < \"2022-01-18T14:50:00Z\") AND` \ - * `(error:*)` - Returns operations where: - * * The operation's metadata type is - * [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata]. - * * The source backup name contains the string "test". - * * The operation started before 2022-01-18T14:50:00Z. - * * The operation resulted in an error. - * * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ - * `(metadata.database:test_db)) OR` \ - * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) - * AND` \ - * `(metadata.source_backup:test_bkp)) AND` \ - * `(error:*)` - Returns operations where: - * * The operation's metadata matches either of criteria: - * * The operation's metadata type is - * [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] - * AND the source database name of the backup contains the string - * "test_db" - * * The operation's metadata type is - * [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata] - * AND the source backup name contains the string "test_bkp" - * * The operation resulted in an error. - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listBackupOperations($parent, array $optionalArgs = []) - { - $request = new ListBackupOperationsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListBackupOperations', - $optionalArgs, - ListBackupOperationsResponse::class, - $request - ); - } - - /** - * Lists all the backup schedules for the database. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listBackupSchedules($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listBackupSchedules($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. Database is the parent resource whose backup schedules should be - * listed. Values are of the form - * projects//instances//databases/ - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listBackupSchedules($parent, array $optionalArgs = []) - { - $request = new ListBackupSchedulesRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListBackupSchedules', - $optionalArgs, - ListBackupSchedulesResponse::class, - $request - ); - } - - /** - * Lists completed and pending backups. - * Backups returned are ordered by `create_time` in descending order, - * starting from the most recent `create_time`. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listBackups($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listBackups($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The instance to list backups from. Values are of the - * form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $filter - * An expression that filters the list of returned backups. - * - * A filter expression consists of a field name, a comparison operator, and a - * value for filtering. - * The value must be a string, a number, or a boolean. The comparison operator - * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. - * Colon `:` is the contains operator. Filter rules are not case sensitive. - * - * The following fields in the - * [Backup][google.spanner.admin.database.v1.Backup] are eligible for - * filtering: - * - * * `name` - * * `database` - * * `state` - * * `create_time` (and values are of the format YYYY-MM-DDTHH:MM:SSZ) - * * `expire_time` (and values are of the format YYYY-MM-DDTHH:MM:SSZ) - * * `version_time` (and values are of the format YYYY-MM-DDTHH:MM:SSZ) - * * `size_bytes` - * * `backup_schedules` - * - * You can combine multiple expressions by enclosing each expression in - * parentheses. By default, expressions are combined with AND logic, but - * you can specify AND, OR, and NOT logic explicitly. - * - * Here are a few examples: - * - * * `name:Howl` - The backup's name contains the string "howl". - * * `database:prod` - * - The database's name contains the string "prod". - * * `state:CREATING` - The backup is pending creation. - * * `state:READY` - The backup is fully created and ready for use. - * * `(name:howl) AND (create_time < \"2018-03-28T14:50:00Z\")` - * - The backup name contains the string "howl" and `create_time` - * of the backup is before 2018-03-28T14:50:00Z. - * * `expire_time < \"2018-03-28T14:50:00Z\"` - * - The backup `expire_time` is before 2018-03-28T14:50:00Z. - * * `size_bytes > 10000000000` - The backup's size is greater than 10GB - * * `backup_schedules:daily` - * - The backup is created from a schedule with "daily" in its name. - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listBackups($parent, array $optionalArgs = []) - { - $request = new ListBackupsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListBackups', - $optionalArgs, - ListBackupsResponse::class, - $request - ); - } - - /** - * Lists database [longrunning-operations][google.longrunning.Operation]. - * A database operation has a name of the form - * `projects//instances//databases//operations/`. - * The long-running operation - * [metadata][google.longrunning.Operation.metadata] field type - * `metadata.type_url` describes the type of the metadata. Operations returned - * include those that have completed/failed/canceled within the last 7 days, - * and pending operations. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listDatabaseOperations($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listDatabaseOperations($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The instance of the database operations. - * Values are of the form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $filter - * An expression that filters the list of returned operations. - * - * A filter expression consists of a field name, a - * comparison operator, and a value for filtering. - * The value must be a string, a number, or a boolean. The comparison operator - * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. - * Colon `:` is the contains operator. Filter rules are not case sensitive. - * - * The following fields in the [Operation][google.longrunning.Operation] - * are eligible for filtering: - * - * * `name` - The name of the long-running operation - * * `done` - False if the operation is in progress, else true. - * * `metadata.@type` - the type of metadata. For example, the type string - * for - * [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata] - * is - * `type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata`. - * * `metadata.` - any field in metadata.value. - * `metadata.@type` must be specified first, if filtering on metadata - * fields. - * * `error` - Error associated with the long-running operation. - * * `response.@type` - the type of response. - * * `response.` - any field in response.value. - * - * You can combine multiple expressions by enclosing each expression in - * parentheses. By default, expressions are combined with AND logic. However, - * you can specify AND, OR, and NOT logic explicitly. - * - * Here are a few examples: - * - * * `done:true` - The operation is complete. - * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata) AND` \ - * `(metadata.source_type:BACKUP) AND` \ - * `(metadata.backup_info.backup:backup_howl) AND` \ - * `(metadata.name:restored_howl) AND` \ - * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ - * `(error:*)` - Return operations where: - * * The operation's metadata type is - * [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. - * * The database is restored from a backup. - * * The backup name contains "backup_howl". - * * The restored database's name contains "restored_howl". - * * The operation started before 2018-03-28T14:50:00Z. - * * The operation resulted in an error. - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listDatabaseOperations($parent, array $optionalArgs = []) - { - $request = new ListDatabaseOperationsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListDatabaseOperations', - $optionalArgs, - ListDatabaseOperationsResponse::class, - $request - ); - } - - /** - * Lists Cloud Spanner database roles. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listDatabaseRoles($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listDatabaseRoles($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The database whose roles should be listed. - * Values are of the form - * `projects//instances//databases/`. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listDatabaseRoles($parent, array $optionalArgs = []) - { - $request = new ListDatabaseRolesRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListDatabaseRoles', - $optionalArgs, - ListDatabaseRolesResponse::class, - $request - ); - } - - /** - * Lists Cloud Spanner databases. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $databaseAdminClient->listDatabases($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $databaseAdminClient->listDatabases($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The instance whose databases should be listed. - * Values are of the form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listDatabases($parent, array $optionalArgs = []) - { - $request = new ListDatabasesRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListDatabases', - $optionalArgs, - ListDatabasesResponse::class, - $request - ); - } - - /** - * Create a new database by restoring from a completed backup. The new - * database must be in the same project and in an instance with the same - * instance configuration as the instance containing - * the backup. The returned database [long-running - * operation][google.longrunning.Operation] has a name of the format - * `projects//instances//databases//operations/`, - * and can be used to track the progress of the operation, and to cancel it. - * The [metadata][google.longrunning.Operation.metadata] field type is - * [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. - * The [response][google.longrunning.Operation.response] type - * is [Database][google.spanner.admin.database.v1.Database], if - * successful. Cancelling the returned operation will stop the restore and - * delete the database. - * There can be only one database being restored into an instance at a time. - * Once the restore operation completes, a new restore operation can be - * initiated, without waiting for the optimize operation associated with the - * first restore to complete. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedParent = $databaseAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $databaseId = 'database_id'; - * $operationResponse = $databaseAdminClient->restoreDatabase($formattedParent, $databaseId); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->restoreDatabase($formattedParent, $databaseId); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'restoreDatabase'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the instance in which to create the - * restored database. This instance must be in the same project and - * have the same instance configuration as the instance containing - * the source backup. Values are of the form - * `projects//instances/`. - * @param string $databaseId Required. The id of the database to create and restore to. This - * database must not already exist. The `database_id` appended to - * `parent` forms the full database name of the form - * `projects//instances//databases/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $backup - * Name of the backup from which to restore. Values are of the form - * `projects//instances//backups/`. - * @type RestoreDatabaseEncryptionConfig $encryptionConfig - * Optional. An encryption configuration describing the encryption type and - * key resources in Cloud KMS used to encrypt/decrypt the database to restore - * to. If this field is not specified, the restored database will use the same - * encryption configuration as the backup by default, namely - * [encryption_type][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type] - * = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function restoreDatabase( - $parent, - $databaseId, - array $optionalArgs = [] - ) { - $request = new RestoreDatabaseRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setDatabaseId($databaseId); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['backup'])) { - $request->setBackup($optionalArgs['backup']); - } - - if (isset($optionalArgs['encryptionConfig'])) { - $request->setEncryptionConfig($optionalArgs['encryptionConfig']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'RestoreDatabase', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Sets the access control policy on a database or backup resource. - * Replaces any existing policy. - * - * Authorization requires `spanner.databases.setIamPolicy` - * permission on [resource][google.iam.v1.SetIamPolicyRequest.resource]. - * For backups, authorization requires `spanner.backups.setIamPolicy` - * permission on [resource][google.iam.v1.SetIamPolicyRequest.resource]. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $resource = 'resource'; - * $policy = new Policy(); - * $response = $databaseAdminClient->setIamPolicy($resource, $policy); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy is being specified. - * See the operation documentation for the appropriate value for this field. - * @param Policy $policy REQUIRED: The complete policy to be applied to the `resource`. The size of - * the policy is limited to a few 10s of KB. An empty policy is a - * valid policy but certain Cloud Platform services (such as Projects) - * might reject them. - * @param array $optionalArgs { - * Optional. - * - * @type FieldMask $updateMask - * OPTIONAL: A FieldMask specifying which fields of the policy to modify. Only - * the fields in the mask will be modified. If no mask is provided, the - * following default mask is used: - * - * `paths: "bindings, etag"` - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\Policy - * - * @throws ApiException if the remote call fails - */ - public function setIamPolicy($resource, $policy, array $optionalArgs = []) - { - $request = new SetIamPolicyRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $request->setPolicy($policy); - $requestParamHeaders['resource'] = $resource; - if (isset($optionalArgs['updateMask'])) { - $request->setUpdateMask($optionalArgs['updateMask']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'SetIamPolicy', - Policy::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Returns permissions that the caller has on the specified database or backup - * resource. - * - * Attempting this RPC on a non-existent Cloud Spanner database will - * result in a NOT_FOUND error if the user has - * `spanner.databases.list` permission on the containing Cloud - * Spanner instance. Otherwise returns an empty set of permissions. - * Calling this method on a backup that does not exist will - * result in a NOT_FOUND error if the user has - * `spanner.backups.list` permission on the containing instance. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $resource = 'resource'; - * $permissions = []; - * $response = $databaseAdminClient->testIamPermissions($resource, $permissions); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy detail is being requested. - * See the operation documentation for the appropriate value for this field. - * @param string[] $permissions The set of permissions to check for the `resource`. Permissions with - * wildcards (such as '*' or 'storage.*') are not allowed. For more - * information see - * [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\TestIamPermissionsResponse - * - * @throws ApiException if the remote call fails - */ - public function testIamPermissions( - $resource, - $permissions, - array $optionalArgs = [] - ) { - $request = new TestIamPermissionsRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $request->setPermissions($permissions); - $requestParamHeaders['resource'] = $resource; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'TestIamPermissions', - TestIamPermissionsResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Updates a pending or completed - * [Backup][google.spanner.admin.database.v1.Backup]. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $backup = new Backup(); - * $updateMask = new FieldMask(); - * $response = $databaseAdminClient->updateBackup($backup, $updateMask); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param Backup $backup Required. The backup to update. `backup.name`, and the fields to be updated - * as specified by `update_mask` are required. Other fields are ignored. - * Update is only supported for the following fields: - * * `backup.expire_time`. - * @param FieldMask $updateMask Required. A mask specifying which fields (e.g. `expire_time`) in the - * Backup resource should be updated. This mask is relative to the Backup - * resource, not to the request message. The field mask must always be - * specified; this prevents any future fields from being erased accidentally - * by clients that do not know about them. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\Backup - * - * @throws ApiException if the remote call fails - */ - public function updateBackup($backup, $updateMask, array $optionalArgs = []) - { - $request = new UpdateBackupRequest(); - $requestParamHeaders = []; - $request->setBackup($backup); - $request->setUpdateMask($updateMask); - $requestParamHeaders['backup.name'] = $backup->getName(); - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'UpdateBackup', - Backup::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Updates a backup schedule. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $backupSchedule = new BackupSchedule(); - * $updateMask = new FieldMask(); - * $response = $databaseAdminClient->updateBackupSchedule($backupSchedule, $updateMask); - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param BackupSchedule $backupSchedule Required. The backup schedule to update. `backup_schedule.name`, and the - * fields to be updated as specified by `update_mask` are required. Other - * fields are ignored. - * @param FieldMask $updateMask Required. A mask specifying which fields in the BackupSchedule resource - * should be updated. This mask is relative to the BackupSchedule resource, - * not to the request message. The field mask must always be - * specified; this prevents any future fields from being erased - * accidentally. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Database\V1\BackupSchedule - * - * @throws ApiException if the remote call fails - */ - public function updateBackupSchedule( - $backupSchedule, - $updateMask, - array $optionalArgs = [] - ) { - $request = new UpdateBackupScheduleRequest(); - $requestParamHeaders = []; - $request->setBackupSchedule($backupSchedule); - $request->setUpdateMask($updateMask); - $requestParamHeaders[ - 'backup_schedule.name' - ] = $backupSchedule->getName(); - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'UpdateBackupSchedule', - BackupSchedule::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Updates a Cloud Spanner database. The returned - * [long-running operation][google.longrunning.Operation] can be used to track - * the progress of updating the database. If the named database does not - * exist, returns `NOT_FOUND`. - * - * While the operation is pending: - * - * * The database's - * [reconciling][google.spanner.admin.database.v1.Database.reconciling] - * field is set to true. - * * Cancelling the operation is best-effort. If the cancellation succeeds, - * the operation metadata's - * [cancel_time][google.spanner.admin.database.v1.UpdateDatabaseMetadata.cancel_time] - * is set, the updates are reverted, and the operation terminates with a - * `CANCELLED` status. - * * New UpdateDatabase requests will return a `FAILED_PRECONDITION` error - * until the pending operation is done (returns successfully or with - * error). - * * Reading the database via the API continues to give the pre-request - * values. - * - * Upon completion of the returned operation: - * - * * The new values are in effect and readable via the API. - * * The database's - * [reconciling][google.spanner.admin.database.v1.Database.reconciling] - * field becomes false. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format - * `projects//instances//databases//operations/` - * and can be used to track the database modification. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [UpdateDatabaseMetadata][google.spanner.admin.database.v1.UpdateDatabaseMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Database][google.spanner.admin.database.v1.Database], if successful. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $database = new Database(); - * $updateMask = new FieldMask(); - * $operationResponse = $databaseAdminClient->updateDatabase($database, $updateMask); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->updateDatabase($database, $updateMask); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'updateDatabase'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param Database $database Required. The database to update. - * The `name` field of the database is of the form - * `projects//instances//databases/`. - * @param FieldMask $updateMask Required. The list of fields to update. Currently, only - * `enable_drop_protection` field can be updated. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function updateDatabase( - $database, - $updateMask, - array $optionalArgs = [] - ) { - $request = new UpdateDatabaseRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $request->setUpdateMask($updateMask); - $requestParamHeaders['database.name'] = $database->getName(); - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'UpdateDatabase', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Updates the schema of a Cloud Spanner database by - * creating/altering/dropping tables, columns, indexes, etc. The returned - * [long-running operation][google.longrunning.Operation] will have a name of - * the format `/operations/` and can be used to - * track execution of the schema change(s). The - * [metadata][google.longrunning.Operation.metadata] field type is - * [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. - * The operation has no response. - * - * Sample code: - * ``` - * $databaseAdminClient = new DatabaseAdminClient(); - * try { - * $formattedDatabase = $databaseAdminClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $statements = []; - * $operationResponse = $databaseAdminClient->updateDatabaseDdl($formattedDatabase, $statements); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * // operation succeeded and returns no value - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $databaseAdminClient->updateDatabaseDdl($formattedDatabase, $statements); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $databaseAdminClient->resumeOperation($operationName, 'updateDatabaseDdl'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * // operation succeeded and returns no value - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $databaseAdminClient->close(); - * } - * ``` - * - * @param string $database Required. The database to update. - * @param string[] $statements Required. DDL statements to be applied to the database. - * @param array $optionalArgs { - * Optional. - * - * @type string $operationId - * If empty, the new update request is assigned an - * automatically-generated operation ID. Otherwise, `operation_id` - * is used to construct the name of the resulting - * [Operation][google.longrunning.Operation]. - * - * Specifying an explicit operation ID simplifies determining - * whether the statements were executed in the event that the - * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] - * call is replayed, or the return value is otherwise lost: the - * [database][google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.database] - * and `operation_id` fields can be combined to form the - * [name][google.longrunning.Operation.name] of the resulting - * [longrunning.Operation][google.longrunning.Operation]: - * `/operations/`. - * - * `operation_id` should be unique within the database, and must be - * a valid identifier: `[a-z][a-z0-9_]*`. Note that - * automatically-generated operation IDs always begin with an - * underscore. If the named operation already exists, - * [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] - * returns `ALREADY_EXISTS`. - * @type string $protoDescriptors - * Optional. Proto descriptors used by CREATE/ALTER PROTO BUNDLE statements. - * Contains a protobuf-serialized - * [google.protobuf.FileDescriptorSet](https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto). - * To generate it, [install](https://grpc.io/docs/protoc-installation/) and - * run `protoc` with --include_imports and --descriptor_set_out. For example, - * to generate for moon/shot/app.proto, run - * ``` - * $protoc --proto_path=/app_path --proto_path=/lib_path \ - * --include_imports \ - * --descriptor_set_out=descriptors.data \ - * moon/shot/app.proto - * ``` - * For more details, see protobuffer [self - * description](https://developers.google.com/protocol-buffers/docs/techniques#self-description). - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function updateDatabaseDdl( - $database, - $statements, - array $optionalArgs = [] - ) { - $request = new UpdateDatabaseDdlRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $request->setStatements($statements); - $requestParamHeaders['database'] = $database; - if (isset($optionalArgs['operationId'])) { - $request->setOperationId($optionalArgs['operationId']); - } - - if (isset($optionalArgs['protoDescriptors'])) { - $request->setProtoDescriptors($optionalArgs['protoDescriptors']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'UpdateDatabaseDdl', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } -} diff --git a/Spanner/src/Admin/Instance/V1/Gapic/InstanceAdminGapicClient.php b/Spanner/src/Admin/Instance/V1/Gapic/InstanceAdminGapicClient.php deleted file mode 100644 index 4e93f047478a..000000000000 --- a/Spanner/src/Admin/Instance/V1/Gapic/InstanceAdminGapicClient.php +++ /dev/null @@ -1,2526 +0,0 @@ -projectName('[PROJECT]'); - * $instanceId = 'instance_id'; - * $instance = new Instance(); - * $operationResponse = $instanceAdminClient->createInstance($formattedParent, $instanceId, $instance); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->createInstance($formattedParent, $instanceId, $instance); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'createInstance'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * Many parameters require resource names to be formatted in a particular way. To - * assist with these names, this class includes a format method for each type of - * name, and additionally a parseName method to extract the individual identifiers - * contained within formatted names that are returned by the API. - * - * @deprecated Please use the new service client {@see \Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient}. - */ -class InstanceAdminGapicClient -{ - use GapicClientTrait; - - /** The name of the service. */ - const SERVICE_NAME = 'google.spanner.admin.instance.v1.InstanceAdmin'; - - /** - * The default address of the service. - * - * @deprecated SERVICE_ADDRESS_TEMPLATE should be used instead. - */ - const SERVICE_ADDRESS = 'spanner.googleapis.com'; - - /** The address template of the service. */ - private const SERVICE_ADDRESS_TEMPLATE = 'spanner.UNIVERSE_DOMAIN'; - - /** The default port of the service. */ - const DEFAULT_SERVICE_PORT = 443; - - /** The name of the code generator, to be included in the agent header. */ - const CODEGEN_NAME = 'gapic'; - - /** The default scopes required by the service. */ - public static $serviceScopes = [ - 'https://www.googleapis.com/auth/cloud-platform', - 'https://www.googleapis.com/auth/spanner.admin', - ]; - - private static $instanceNameTemplate; - - private static $instanceConfigNameTemplate; - - private static $instancePartitionNameTemplate; - - private static $projectNameTemplate; - - private static $pathTemplateMap; - - private $operationsClient; - - private static function getClientDefaults() - { - return [ - 'serviceName' => self::SERVICE_NAME, - 'apiEndpoint' => - self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, - 'clientConfig' => - __DIR__ . '/../resources/instance_admin_client_config.json', - 'descriptorsConfigPath' => - __DIR__ . '/../resources/instance_admin_descriptor_config.php', - 'gcpApiConfigPath' => - __DIR__ . '/../resources/instance_admin_grpc_config.json', - 'credentialsConfig' => [ - 'defaultScopes' => self::$serviceScopes, - ], - 'transportConfig' => [ - 'rest' => [ - 'restClientConfigPath' => - __DIR__ . - '/../resources/instance_admin_rest_client_config.php', - ], - ], - ]; - } - - private static function getInstanceNameTemplate() - { - if (self::$instanceNameTemplate == null) { - self::$instanceNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}' - ); - } - - return self::$instanceNameTemplate; - } - - private static function getInstanceConfigNameTemplate() - { - if (self::$instanceConfigNameTemplate == null) { - self::$instanceConfigNameTemplate = new PathTemplate( - 'projects/{project}/instanceConfigs/{instance_config}' - ); - } - - return self::$instanceConfigNameTemplate; - } - - private static function getInstancePartitionNameTemplate() - { - if (self::$instancePartitionNameTemplate == null) { - self::$instancePartitionNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/instancePartitions/{instance_partition}' - ); - } - - return self::$instancePartitionNameTemplate; - } - - private static function getProjectNameTemplate() - { - if (self::$projectNameTemplate == null) { - self::$projectNameTemplate = new PathTemplate('projects/{project}'); - } - - return self::$projectNameTemplate; - } - - private static function getPathTemplateMap() - { - if (self::$pathTemplateMap == null) { - self::$pathTemplateMap = [ - 'instance' => self::getInstanceNameTemplate(), - 'instanceConfig' => self::getInstanceConfigNameTemplate(), - 'instancePartition' => self::getInstancePartitionNameTemplate(), - 'project' => self::getProjectNameTemplate(), - ]; - } - - return self::$pathTemplateMap; - } - - /** - * Formats a string containing the fully-qualified path to represent a instance - * resource. - * - * @param string $project - * @param string $instance - * - * @return string The formatted instance resource. - */ - public static function instanceName($project, $instance) - { - return self::getInstanceNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a - * instance_config resource. - * - * @param string $project - * @param string $instanceConfig - * - * @return string The formatted instance_config resource. - */ - public static function instanceConfigName($project, $instanceConfig) - { - return self::getInstanceConfigNameTemplate()->render([ - 'project' => $project, - 'instance_config' => $instanceConfig, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a - * instance_partition resource. - * - * @param string $project - * @param string $instance - * @param string $instancePartition - * - * @return string The formatted instance_partition resource. - */ - public static function instancePartitionName( - $project, - $instance, - $instancePartition - ) { - return self::getInstancePartitionNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'instance_partition' => $instancePartition, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a project - * resource. - * - * @param string $project - * - * @return string The formatted project resource. - */ - public static function projectName($project) - { - return self::getProjectNameTemplate()->render([ - 'project' => $project, - ]); - } - - /** - * Parses a formatted name string and returns an associative array of the components in the name. - * The following name formats are supported: - * Template: Pattern - * - instance: projects/{project}/instances/{instance} - * - instanceConfig: projects/{project}/instanceConfigs/{instance_config} - * - instancePartition: projects/{project}/instances/{instance}/instancePartitions/{instance_partition} - * - project: projects/{project} - * - * The optional $template argument can be supplied to specify a particular pattern, - * and must match one of the templates listed above. If no $template argument is - * provided, or if the $template argument does not match one of the templates - * listed, then parseName will check each of the supported templates, and return - * the first match. - * - * @param string $formattedName The formatted name string - * @param string $template Optional name of template to match - * - * @return array An associative array from name component IDs to component values. - * - * @throws ValidationException If $formattedName could not be matched. - */ - public static function parseName($formattedName, $template = null) - { - $templateMap = self::getPathTemplateMap(); - if ($template) { - if (!isset($templateMap[$template])) { - throw new ValidationException( - "Template name $template does not exist" - ); - } - - return $templateMap[$template]->match($formattedName); - } - - foreach ($templateMap as $templateName => $pathTemplate) { - try { - return $pathTemplate->match($formattedName); - } catch (ValidationException $ex) { - // Swallow the exception to continue trying other path templates - } - } - - throw new ValidationException( - "Input did not match any known format. Input: $formattedName" - ); - } - - /** - * Return an OperationsClient object with the same endpoint as $this. - * - * @return OperationsClient - */ - public function getOperationsClient() - { - return $this->operationsClient; - } - - /** - * Resume an existing long running operation that was previously started by a long - * running API method. If $methodName is not provided, or does not match a long - * running API method, then the operation can still be resumed, but the - * OperationResponse object will not deserialize the final response. - * - * @param string $operationName The name of the long running operation - * @param string $methodName The name of the method used to start the operation - * - * @return OperationResponse - */ - public function resumeOperation($operationName, $methodName = null) - { - $options = isset($this->descriptors[$methodName]['longRunning']) - ? $this->descriptors[$methodName]['longRunning'] - : []; - $operation = new OperationResponse( - $operationName, - $this->getOperationsClient(), - $options - ); - $operation->reload(); - return $operation; - } - - /** - * Constructor. - * - * @param array $options { - * Optional. Options for configuring the service API wrapper. - * - * @type string $apiEndpoint - * The address of the API remote host. May optionally include the port, formatted - * as ":". Default 'spanner.googleapis.com:443'. - * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials - * The credentials to be used by the client to authorize API calls. This option - * accepts either a path to a credentials file, or a decoded credentials file as a - * PHP array. - * *Advanced usage*: In addition, this option can also accept a pre-constructed - * {@see \Google\Auth\FetchAuthTokenInterface} object or - * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these - * objects are provided, any settings in $credentialsConfig will be ignored. - * @type array $credentialsConfig - * Options used to configure credentials, including auth token caching, for the - * client. For a full list of supporting configuration options, see - * {@see \Google\ApiCore\CredentialsWrapper::build()} . - * @type bool $disableRetries - * Determines whether or not retries defined by the client configuration should be - * disabled. Defaults to `false`. - * @type string|array $clientConfig - * Client method configuration, including retry settings. This option can be either - * a path to a JSON file, or a PHP array containing the decoded JSON data. By - * default this settings points to the default client config file, which is - * provided in the resources folder. - * @type string|TransportInterface $transport - * The transport used for executing network requests. May be either the string - * `rest` or `grpc`. Defaults to `grpc` if gRPC support is detected on the system. - * *Advanced usage*: Additionally, it is possible to pass in an already - * instantiated {@see \Google\ApiCore\Transport\TransportInterface} object. Note - * that when this object is provided, any settings in $transportConfig, and any - * $apiEndpoint setting, will be ignored. - * @type array $transportConfig - * Configuration options that will be used to construct the transport. Options for - * each supported transport type should be passed in a key for that transport. For - * example: - * $transportConfig = [ - * 'grpc' => [...], - * 'rest' => [...], - * ]; - * See the {@see \Google\ApiCore\Transport\GrpcTransport::build()} and - * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the - * supported options. - * @type callable $clientCertSource - * A callable which returns the client cert as a string. This can be used to - * provide a certificate and private key to the transport layer for mTLS. - * } - * - * @throws ValidationException - */ - public function __construct(array $options = []) - { - $clientOptions = $this->buildClientOptions($options); - $this->setClientOptions($clientOptions); - $this->operationsClient = $this->createOperationsClient($clientOptions); - } - - /** - * Creates an instance and begins preparing it to begin serving. The - * returned [long-running operation][google.longrunning.Operation] - * can be used to track the progress of preparing the new - * instance. The instance name is assigned by the caller. If the - * named instance already exists, `CreateInstance` returns - * `ALREADY_EXISTS`. - * - * Immediately upon completion of this request: - * - * * The instance is readable via the API, with all requested attributes - * but no allocated resources. Its state is `CREATING`. - * - * Until completion of the returned operation: - * - * * Cancelling the operation renders the instance immediately unreadable - * via the API. - * * The instance can be deleted. - * * All other attempts to modify the instance are rejected. - * - * Upon completion of the returned operation: - * - * * Billing for all successfully-allocated resources begins (some types - * may have lower than the requested levels). - * * Databases can be created in the instance. - * * The instance's allocated resource levels are readable via the API. - * * The instance's state becomes `READY`. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format `/operations/` and - * can be used to track creation of the instance. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [CreateInstanceMetadata][google.spanner.admin.instance.v1.CreateInstanceMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Instance][google.spanner.admin.instance.v1.Instance], if successful. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->projectName('[PROJECT]'); - * $instanceId = 'instance_id'; - * $instance = new Instance(); - * $operationResponse = $instanceAdminClient->createInstance($formattedParent, $instanceId, $instance); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->createInstance($formattedParent, $instanceId, $instance); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'createInstance'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the project in which to create the instance. Values - * are of the form `projects/`. - * @param string $instanceId Required. The ID of the instance to create. Valid identifiers are of the - * form `[a-z][-a-z0-9]*[a-z0-9]` and must be between 2 and 64 characters in - * length. - * @param Instance $instance Required. The instance to create. The name may be omitted, but if - * specified must be `/instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function createInstance( - $parent, - $instanceId, - $instance, - array $optionalArgs = [] - ) { - $request = new CreateInstanceRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setInstanceId($instanceId); - $request->setInstance($instance); - $requestParamHeaders['parent'] = $parent; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CreateInstance', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Creates an instance configuration and begins preparing it to be used. The - * returned [long-running operation][google.longrunning.Operation] - * can be used to track the progress of preparing the new - * instance configuration. The instance configuration name is assigned by the - * caller. If the named instance configuration already exists, - * `CreateInstanceConfig` returns `ALREADY_EXISTS`. - * - * Immediately after the request returns: - * - * * The instance configuration is readable via the API, with all requested - * attributes. The instance configuration's - * [reconciling][google.spanner.admin.instance.v1.InstanceConfig.reconciling] - * field is set to true. Its state is `CREATING`. - * - * While the operation is pending: - * - * * Cancelling the operation renders the instance configuration immediately - * unreadable via the API. - * * Except for deleting the creating resource, all other attempts to modify - * the instance configuration are rejected. - * - * Upon completion of the returned operation: - * - * * Instances can be created using the instance configuration. - * * The instance configuration's - * [reconciling][google.spanner.admin.instance.v1.InstanceConfig.reconciling] - * field becomes false. Its state becomes `READY`. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format - * `/operations/` and can be used to track - * creation of the instance configuration. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [CreateInstanceConfigMetadata][google.spanner.admin.instance.v1.CreateInstanceConfigMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig], if - * successful. - * - * Authorization requires `spanner.instanceConfigs.create` permission on - * the resource - * [parent][google.spanner.admin.instance.v1.CreateInstanceConfigRequest.parent]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->projectName('[PROJECT]'); - * $instanceConfigId = 'instance_config_id'; - * $instanceConfig = new InstanceConfig(); - * $operationResponse = $instanceAdminClient->createInstanceConfig($formattedParent, $instanceConfigId, $instanceConfig); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->createInstanceConfig($formattedParent, $instanceConfigId, $instanceConfig); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'createInstanceConfig'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the project in which to create the instance - * configuration. Values are of the form `projects/`. - * @param string $instanceConfigId Required. The ID of the instance configuration to create. Valid identifiers - * are of the form `custom-[-a-z0-9]*[a-z0-9]` and must be between 2 and 64 - * characters in length. The `custom-` prefix is required to avoid name - * conflicts with Google-managed configurations. - * @param InstanceConfig $instanceConfig Required. The InstanceConfig proto of the configuration to create. - * instance_config.name must be - * `/instanceConfigs/`. - * instance_config.base_config must be a Google managed configuration name, - * e.g. /instanceConfigs/us-east1, /instanceConfigs/nam3. - * @param array $optionalArgs { - * Optional. - * - * @type bool $validateOnly - * An option to validate, but not actually execute, a request, - * and provide the same response. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function createInstanceConfig( - $parent, - $instanceConfigId, - $instanceConfig, - array $optionalArgs = [] - ) { - $request = new CreateInstanceConfigRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setInstanceConfigId($instanceConfigId); - $request->setInstanceConfig($instanceConfig); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['validateOnly'])) { - $request->setValidateOnly($optionalArgs['validateOnly']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CreateInstanceConfig', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Creates an instance partition and begins preparing it to be used. The - * returned [long-running operation][google.longrunning.Operation] - * can be used to track the progress of preparing the new instance partition. - * The instance partition name is assigned by the caller. If the named - * instance partition already exists, `CreateInstancePartition` returns - * `ALREADY_EXISTS`. - * - * Immediately upon completion of this request: - * - * * The instance partition is readable via the API, with all requested - * attributes but no allocated resources. Its state is `CREATING`. - * - * Until completion of the returned operation: - * - * * Cancelling the operation renders the instance partition immediately - * unreadable via the API. - * * The instance partition can be deleted. - * * All other attempts to modify the instance partition are rejected. - * - * Upon completion of the returned operation: - * - * * Billing for all successfully-allocated resources begins (some types - * may have lower than the requested levels). - * * Databases can start using this instance partition. - * * The instance partition's allocated resource levels are readable via the - * API. - * * The instance partition's state becomes `READY`. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format - * `/operations/` and can be used to - * track creation of the instance partition. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [CreateInstancePartitionMetadata][google.spanner.admin.instance.v1.CreateInstancePartitionMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [InstancePartition][google.spanner.admin.instance.v1.InstancePartition], if - * successful. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $instancePartitionId = 'instance_partition_id'; - * $instancePartition = new InstancePartition(); - * $operationResponse = $instanceAdminClient->createInstancePartition($formattedParent, $instancePartitionId, $instancePartition); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->createInstancePartition($formattedParent, $instancePartitionId, $instancePartition); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'createInstancePartition'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the instance in which to create the instance - * partition. Values are of the form - * `projects//instances/`. - * @param string $instancePartitionId Required. The ID of the instance partition to create. Valid identifiers are - * of the form `[a-z][-a-z0-9]*[a-z0-9]` and must be between 2 and 64 - * characters in length. - * @param InstancePartition $instancePartition Required. The instance partition to create. The instance_partition.name may - * be omitted, but if specified must be - * `/instancePartitions/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function createInstancePartition( - $parent, - $instancePartitionId, - $instancePartition, - array $optionalArgs = [] - ) { - $request = new CreateInstancePartitionRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $request->setInstancePartitionId($instancePartitionId); - $request->setInstancePartition($instancePartition); - $requestParamHeaders['parent'] = $parent; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'CreateInstancePartition', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Deletes an instance. - * - * Immediately upon completion of the request: - * - * * Billing ceases for all of the instance's reserved resources. - * - * Soon afterward: - * - * * The instance and *all of its databases* immediately and - * irrevocably disappear from the API. All data in the databases - * is permanently deleted. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $instanceAdminClient->deleteInstance($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the instance to be deleted. Values are of the form - * `projects//instances/` - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteInstance($name, array $optionalArgs = []) - { - $request = new DeleteInstanceRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteInstance', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Deletes the instance configuration. Deletion is only allowed when no - * instances are using the configuration. If any instances are using - * the configuration, returns `FAILED_PRECONDITION`. - * - * Only user-managed configurations can be deleted. - * - * Authorization requires `spanner.instanceConfigs.delete` permission on - * the resource [name][google.spanner.admin.instance.v1.InstanceConfig.name]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instanceConfigName('[PROJECT]', '[INSTANCE_CONFIG]'); - * $instanceAdminClient->deleteInstanceConfig($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the instance configuration to be deleted. - * Values are of the form - * `projects//instanceConfigs/` - * @param array $optionalArgs { - * Optional. - * - * @type string $etag - * Used for optimistic concurrency control as a way to help prevent - * simultaneous deletes of an instance configuration from overwriting each - * other. If not empty, the API - * only deletes the instance configuration when the etag provided matches the - * current status of the requested instance configuration. Otherwise, deletes - * the instance configuration without checking the current status of the - * requested instance configuration. - * @type bool $validateOnly - * An option to validate, but not actually execute, a request, - * and provide the same response. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteInstanceConfig($name, array $optionalArgs = []) - { - $request = new DeleteInstanceConfigRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - if (isset($optionalArgs['etag'])) { - $request->setEtag($optionalArgs['etag']); - } - - if (isset($optionalArgs['validateOnly'])) { - $request->setValidateOnly($optionalArgs['validateOnly']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteInstanceConfig', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Deletes an existing instance partition. Requires that the - * instance partition is not used by any database or backup and is not the - * default instance partition of an instance. - * - * Authorization requires `spanner.instancePartitions.delete` permission on - * the resource - * [name][google.spanner.admin.instance.v1.InstancePartition.name]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instancePartitionName('[PROJECT]', '[INSTANCE]', '[INSTANCE_PARTITION]'); - * $instanceAdminClient->deleteInstancePartition($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the instance partition to be deleted. - * Values are of the form - * `projects/{project}/instances/{instance}/instancePartitions/{instance_partition}` - * @param array $optionalArgs { - * Optional. - * - * @type string $etag - * Optional. If not empty, the API only deletes the instance partition when - * the etag provided matches the current status of the requested instance - * partition. Otherwise, deletes the instance partition without checking the - * current status of the requested instance partition. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteInstancePartition($name, array $optionalArgs = []) - { - $request = new DeleteInstancePartitionRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - if (isset($optionalArgs['etag'])) { - $request->setEtag($optionalArgs['etag']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteInstancePartition', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets the access control policy for an instance resource. Returns an empty - * policy if an instance exists but does not have a policy set. - * - * Authorization requires `spanner.instances.getIamPolicy` on - * [resource][google.iam.v1.GetIamPolicyRequest.resource]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $resource = 'resource'; - * $response = $instanceAdminClient->getIamPolicy($resource); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy is being requested. - * See the operation documentation for the appropriate value for this field. - * @param array $optionalArgs { - * Optional. - * - * @type GetPolicyOptions $options - * OPTIONAL: A `GetPolicyOptions` object for specifying options to - * `GetIamPolicy`. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\Policy - * - * @throws ApiException if the remote call fails - */ - public function getIamPolicy($resource, array $optionalArgs = []) - { - $request = new GetIamPolicyRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $requestParamHeaders['resource'] = $resource; - if (isset($optionalArgs['options'])) { - $request->setOptions($optionalArgs['options']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetIamPolicy', - Policy::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets information about a particular instance. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $response = $instanceAdminClient->getInstance($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the requested instance. Values are of the form - * `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type FieldMask $fieldMask - * If field_mask is present, specifies the subset of - * [Instance][google.spanner.admin.instance.v1.Instance] fields that should be - * returned. If absent, all - * [Instance][google.spanner.admin.instance.v1.Instance] fields are returned. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Instance\V1\Instance - * - * @throws ApiException if the remote call fails - */ - public function getInstance($name, array $optionalArgs = []) - { - $request = new GetInstanceRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - if (isset($optionalArgs['fieldMask'])) { - $request->setFieldMask($optionalArgs['fieldMask']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetInstance', - Instance::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets information about a particular instance configuration. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instanceConfigName('[PROJECT]', '[INSTANCE_CONFIG]'); - * $response = $instanceAdminClient->getInstanceConfig($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the requested instance configuration. Values are of - * the form `projects//instanceConfigs/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Instance\V1\InstanceConfig - * - * @throws ApiException if the remote call fails - */ - public function getInstanceConfig($name, array $optionalArgs = []) - { - $request = new GetInstanceConfigRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetInstanceConfig', - InstanceConfig::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Gets information about a particular instance partition. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instancePartitionName('[PROJECT]', '[INSTANCE]', '[INSTANCE_PARTITION]'); - * $response = $instanceAdminClient->getInstancePartition($formattedName); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the requested instance partition. Values are of - * the form - * `projects/{project}/instances/{instance}/instancePartitions/{instance_partition}`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\Admin\Instance\V1\InstancePartition - * - * @throws ApiException if the remote call fails - */ - public function getInstancePartition($name, array $optionalArgs = []) - { - $request = new GetInstancePartitionRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetInstancePartition', - InstancePartition::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Lists the user-managed instance configuration [long-running - * operations][google.longrunning.Operation] in the given project. An instance - * configuration operation has a name of the form - * `projects//instanceConfigs//operations/`. - * The long-running operation - * [metadata][google.longrunning.Operation.metadata] field type - * `metadata.type_url` describes the type of the metadata. Operations returned - * include those that have completed/failed/canceled within the last 7 days, - * and pending operations. Operations returned are ordered by - * `operation.metadata.value.start_time` in descending order starting - * from the most recently started operation. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->projectName('[PROJECT]'); - * // Iterate over pages of elements - * $pagedResponse = $instanceAdminClient->listInstanceConfigOperations($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $instanceAdminClient->listInstanceConfigOperations($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The project of the instance configuration operations. - * Values are of the form `projects/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $filter - * An expression that filters the list of returned operations. - * - * A filter expression consists of a field name, a - * comparison operator, and a value for filtering. - * The value must be a string, a number, or a boolean. The comparison operator - * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. - * Colon `:` is the contains operator. Filter rules are not case sensitive. - * - * The following fields in the [Operation][google.longrunning.Operation] - * are eligible for filtering: - * - * * `name` - The name of the long-running operation - * * `done` - False if the operation is in progress, else true. - * * `metadata.@type` - the type of metadata. For example, the type string - * for - * [CreateInstanceConfigMetadata][google.spanner.admin.instance.v1.CreateInstanceConfigMetadata] - * is - * `type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata`. - * * `metadata.` - any field in metadata.value. - * `metadata.@type` must be specified first, if filtering on metadata - * fields. - * * `error` - Error associated with the long-running operation. - * * `response.@type` - the type of response. - * * `response.` - any field in response.value. - * - * You can combine multiple expressions by enclosing each expression in - * parentheses. By default, expressions are combined with AND logic. However, - * you can specify AND, OR, and NOT logic explicitly. - * - * Here are a few examples: - * - * * `done:true` - The operation is complete. - * * `(metadata.@type=` \ - * `type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata) - * AND` \ - * `(metadata.instance_config.name:custom-config) AND` \ - * `(metadata.progress.start_time < \"2021-03-28T14:50:00Z\") AND` \ - * `(error:*)` - Return operations where: - * * The operation's metadata type is - * [CreateInstanceConfigMetadata][google.spanner.admin.instance.v1.CreateInstanceConfigMetadata]. - * * The instance configuration name contains "custom-config". - * * The operation started before 2021-03-28T14:50:00Z. - * * The operation resulted in an error. - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listInstanceConfigOperations( - $parent, - array $optionalArgs = [] - ) { - $request = new ListInstanceConfigOperationsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListInstanceConfigOperations', - $optionalArgs, - ListInstanceConfigOperationsResponse::class, - $request - ); - } - - /** - * Lists the supported instance configurations for a given project. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->projectName('[PROJECT]'); - * // Iterate over pages of elements - * $pagedResponse = $instanceAdminClient->listInstanceConfigs($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $instanceAdminClient->listInstanceConfigs($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the project for which a list of supported instance - * configurations is requested. Values are of the form - * `projects/`. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listInstanceConfigs($parent, array $optionalArgs = []) - { - $request = new ListInstanceConfigsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListInstanceConfigs', - $optionalArgs, - ListInstanceConfigsResponse::class, - $request - ); - } - - /** - * Lists instance partition [long-running - * operations][google.longrunning.Operation] in the given instance. - * An instance partition operation has a name of the form - * `projects//instances//instancePartitions//operations/`. - * The long-running operation - * [metadata][google.longrunning.Operation.metadata] field type - * `metadata.type_url` describes the type of the metadata. Operations returned - * include those that have completed/failed/canceled within the last 7 days, - * and pending operations. Operations returned are ordered by - * `operation.metadata.value.start_time` in descending order starting from the - * most recently started operation. - * - * Authorization requires `spanner.instancePartitionOperations.list` - * permission on the resource - * [parent][google.spanner.admin.instance.v1.ListInstancePartitionOperationsRequest.parent]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $instanceAdminClient->listInstancePartitionOperations($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $instanceAdminClient->listInstancePartitionOperations($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The parent instance of the instance partition operations. - * Values are of the form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @type string $filter - * Optional. An expression that filters the list of returned operations. - * - * A filter expression consists of a field name, a - * comparison operator, and a value for filtering. - * The value must be a string, a number, or a boolean. The comparison operator - * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. - * Colon `:` is the contains operator. Filter rules are not case sensitive. - * - * The following fields in the [Operation][google.longrunning.Operation] - * are eligible for filtering: - * - * * `name` - The name of the long-running operation - * * `done` - False if the operation is in progress, else true. - * * `metadata.@type` - the type of metadata. For example, the type string - * for - * [CreateInstancePartitionMetadata][google.spanner.admin.instance.v1.CreateInstancePartitionMetadata] - * is - * `type.googleapis.com/google.spanner.admin.instance.v1.CreateInstancePartitionMetadata`. - * * `metadata.` - any field in metadata.value. - * `metadata.@type` must be specified first, if filtering on metadata - * fields. - * * `error` - Error associated with the long-running operation. - * * `response.@type` - the type of response. - * * `response.` - any field in response.value. - * - * You can combine multiple expressions by enclosing each expression in - * parentheses. By default, expressions are combined with AND logic. However, - * you can specify AND, OR, and NOT logic explicitly. - * - * Here are a few examples: - * - * * `done:true` - The operation is complete. - * * `(metadata.@type=` \ - * `type.googleapis.com/google.spanner.admin.instance.v1.CreateInstancePartitionMetadata) - * AND` \ - * `(metadata.instance_partition.name:custom-instance-partition) AND` \ - * `(metadata.start_time < \"2021-03-28T14:50:00Z\") AND` \ - * `(error:*)` - Return operations where: - * * The operation's metadata type is - * [CreateInstancePartitionMetadata][google.spanner.admin.instance.v1.CreateInstancePartitionMetadata]. - * * The instance partition name contains "custom-instance-partition". - * * The operation started before 2021-03-28T14:50:00Z. - * * The operation resulted in an error. - * @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 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. - * @type Timestamp $instancePartitionDeadline - * Optional. Deadline used while retrieving metadata for instance partition - * operations. Instance partitions whose operation metadata cannot be - * retrieved within this deadline will be added to - * [unreachable][ListInstancePartitionOperationsResponse.unreachable] in - * [ListInstancePartitionOperationsResponse][google.spanner.admin.instance.v1.ListInstancePartitionOperationsResponse]. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listInstancePartitionOperations( - $parent, - array $optionalArgs = [] - ) { - $request = new ListInstancePartitionOperationsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - if (isset($optionalArgs['instancePartitionDeadline'])) { - $request->setInstancePartitionDeadline( - $optionalArgs['instancePartitionDeadline'] - ); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListInstancePartitionOperations', - $optionalArgs, - ListInstancePartitionOperationsResponse::class, - $request - ); - } - - /** - * Lists all instance partitions for the given instance. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * // Iterate over pages of elements - * $pagedResponse = $instanceAdminClient->listInstancePartitions($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $instanceAdminClient->listInstancePartitions($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The instance whose instance partitions should be listed. Values - * are of the form `projects//instances/`. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type Timestamp $instancePartitionDeadline - * Optional. Deadline used while retrieving metadata for instance partitions. - * Instance partitions whose metadata cannot be retrieved within this deadline - * will be added to - * [unreachable][google.spanner.admin.instance.v1.ListInstancePartitionsResponse.unreachable] - * in - * [ListInstancePartitionsResponse][google.spanner.admin.instance.v1.ListInstancePartitionsResponse]. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listInstancePartitions($parent, array $optionalArgs = []) - { - $request = new ListInstancePartitionsRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - if (isset($optionalArgs['instancePartitionDeadline'])) { - $request->setInstancePartitionDeadline( - $optionalArgs['instancePartitionDeadline'] - ); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListInstancePartitions', - $optionalArgs, - ListInstancePartitionsResponse::class, - $request - ); - } - - /** - * Lists all instances in the given project. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedParent = $instanceAdminClient->projectName('[PROJECT]'); - * // Iterate over pages of elements - * $pagedResponse = $instanceAdminClient->listInstances($formattedParent); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $instanceAdminClient->listInstances($formattedParent); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $parent Required. The name of the project for which a list of instances is - * requested. Values are of the form `projects/`. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type string $filter - * An expression for filtering the results of the request. Filter rules are - * case insensitive. The fields eligible for filtering are: - * - * * `name` - * * `display_name` - * * `labels.key` where key is the name of a label - * - * Some examples of using filters are: - * - * * `name:*` --> The instance has a name. - * * `name:Howl` --> The instance's name contains the string "howl". - * * `name:HOWL` --> Equivalent to above. - * * `NAME:howl` --> Equivalent to above. - * * `labels.env:*` --> The instance has the label "env". - * * `labels.env:dev` --> The instance has the label "env" and the value of - * the label contains the string "dev". - * * `name:howl labels.env:dev` --> The instance's name contains "howl" and - * it has the label "env" with its value - * containing "dev". - * @type Timestamp $instanceDeadline - * Deadline used while retrieving metadata for instances. - * Instances whose metadata cannot be retrieved within this deadline will be - * added to - * [unreachable][google.spanner.admin.instance.v1.ListInstancesResponse.unreachable] - * in - * [ListInstancesResponse][google.spanner.admin.instance.v1.ListInstancesResponse]. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listInstances($parent, array $optionalArgs = []) - { - $request = new ListInstancesRequest(); - $requestParamHeaders = []; - $request->setParent($parent); - $requestParamHeaders['parent'] = $parent; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - if (isset($optionalArgs['instanceDeadline'])) { - $request->setInstanceDeadline($optionalArgs['instanceDeadline']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListInstances', - $optionalArgs, - ListInstancesResponse::class, - $request - ); - } - - /** - * Moves an instance to the target instance configuration. You can use the - * returned [long-running operation][google.longrunning.Operation] to track - * the progress of moving the instance. - * - * `MoveInstance` returns `FAILED_PRECONDITION` if the instance meets any of - * the following criteria: - * - * * Is undergoing a move to a different instance configuration - * * Has backups - * * Has an ongoing update - * * Contains any CMEK-enabled databases - * * Is a free trial instance - * - * While the operation is pending: - * - * * All other attempts to modify the instance, including changes to its - * compute capacity, are rejected. - * * The following database and backup admin operations are rejected: - * - * * `DatabaseAdmin.CreateDatabase` - * * `DatabaseAdmin.UpdateDatabaseDdl` (disabled if default_leader is - * specified in the request.) - * * `DatabaseAdmin.RestoreDatabase` - * * `DatabaseAdmin.CreateBackup` - * * `DatabaseAdmin.CopyBackup` - * - * * Both the source and target instance configurations are subject to - * hourly compute and storage charges. - * * The instance might experience higher read-write latencies and a higher - * transaction abort rate. However, moving an instance doesn't cause any - * downtime. - * - * The returned [long-running operation][google.longrunning.Operation] has - * a name of the format - * `/operations/` and can be used to track - * the move instance operation. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [MoveInstanceMetadata][google.spanner.admin.instance.v1.MoveInstanceMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Instance][google.spanner.admin.instance.v1.Instance], - * if successful. - * Cancelling the operation sets its metadata's - * [cancel_time][google.spanner.admin.instance.v1.MoveInstanceMetadata.cancel_time]. - * Cancellation is not immediate because it involves moving any data - * previously moved to the target instance configuration back to the original - * instance configuration. You can use this operation to track the progress of - * the cancellation. Upon successful completion of the cancellation, the - * operation terminates with `CANCELLED` status. - * - * If not cancelled, upon completion of the returned operation: - * - * * The instance successfully moves to the target instance - * configuration. - * * You are billed for compute and storage in target instance - * configuration. - * - * Authorization requires the `spanner.instances.update` permission on - * the resource [instance][google.spanner.admin.instance.v1.Instance]. - * - * For more details, see - * [Move an instance](https://cloud.google.com/spanner/docs/move-instance). - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $formattedName = $instanceAdminClient->instanceName('[PROJECT]', '[INSTANCE]'); - * $formattedTargetConfig = $instanceAdminClient->instanceConfigName('[PROJECT]', '[INSTANCE_CONFIG]'); - * $operationResponse = $instanceAdminClient->moveInstance($formattedName, $formattedTargetConfig); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->moveInstance($formattedName, $formattedTargetConfig); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'moveInstance'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $name Required. The instance to move. - * Values are of the form `projects//instances/`. - * @param string $targetConfig Required. The target instance configuration where to move the instance. - * Values are of the form `projects//instanceConfigs/`. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function moveInstance($name, $targetConfig, array $optionalArgs = []) - { - $request = new MoveInstanceRequest(); - $requestParamHeaders = []; - $request->setName($name); - $request->setTargetConfig($targetConfig); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'MoveInstance', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Sets the access control policy on an instance resource. Replaces any - * existing policy. - * - * Authorization requires `spanner.instances.setIamPolicy` on - * [resource][google.iam.v1.SetIamPolicyRequest.resource]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $resource = 'resource'; - * $policy = new Policy(); - * $response = $instanceAdminClient->setIamPolicy($resource, $policy); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy is being specified. - * See the operation documentation for the appropriate value for this field. - * @param Policy $policy REQUIRED: The complete policy to be applied to the `resource`. The size of - * the policy is limited to a few 10s of KB. An empty policy is a - * valid policy but certain Cloud Platform services (such as Projects) - * might reject them. - * @param array $optionalArgs { - * Optional. - * - * @type FieldMask $updateMask - * OPTIONAL: A FieldMask specifying which fields of the policy to modify. Only - * the fields in the mask will be modified. If no mask is provided, the - * following default mask is used: - * - * `paths: "bindings, etag"` - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\Policy - * - * @throws ApiException if the remote call fails - */ - public function setIamPolicy($resource, $policy, array $optionalArgs = []) - { - $request = new SetIamPolicyRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $request->setPolicy($policy); - $requestParamHeaders['resource'] = $resource; - if (isset($optionalArgs['updateMask'])) { - $request->setUpdateMask($optionalArgs['updateMask']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'SetIamPolicy', - Policy::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Returns permissions that the caller has on the specified instance resource. - * - * Attempting this RPC on a non-existent Cloud Spanner instance resource will - * result in a NOT_FOUND error if the user has `spanner.instances.list` - * permission on the containing Google Cloud Project. Otherwise returns an - * empty set of permissions. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $resource = 'resource'; - * $permissions = []; - * $response = $instanceAdminClient->testIamPermissions($resource, $permissions); - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param string $resource REQUIRED: The resource for which the policy detail is being requested. - * See the operation documentation for the appropriate value for this field. - * @param string[] $permissions The set of permissions to check for the `resource`. Permissions with - * wildcards (such as '*' or 'storage.*') are not allowed. For more - * information see - * [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Iam\V1\TestIamPermissionsResponse - * - * @throws ApiException if the remote call fails - */ - public function testIamPermissions( - $resource, - $permissions, - array $optionalArgs = [] - ) { - $request = new TestIamPermissionsRequest(); - $requestParamHeaders = []; - $request->setResource($resource); - $request->setPermissions($permissions); - $requestParamHeaders['resource'] = $resource; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'TestIamPermissions', - TestIamPermissionsResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Updates an instance, and begins allocating or releasing resources - * as requested. The returned [long-running - * operation][google.longrunning.Operation] can be used to track the - * progress of updating the instance. If the named instance does not - * exist, returns `NOT_FOUND`. - * - * Immediately upon completion of this request: - * - * * For resource types for which a decrease in the instance's allocation - * has been requested, billing is based on the newly-requested level. - * - * Until completion of the returned operation: - * - * * Cancelling the operation sets its metadata's - * [cancel_time][google.spanner.admin.instance.v1.UpdateInstanceMetadata.cancel_time], - * and begins restoring resources to their pre-request values. The - * operation is guaranteed to succeed at undoing all resource changes, - * after which point it terminates with a `CANCELLED` status. - * * All other attempts to modify the instance are rejected. - * * Reading the instance via the API continues to give the pre-request - * resource levels. - * - * Upon completion of the returned operation: - * - * * Billing begins for all successfully-allocated resources (some types - * may have lower than the requested levels). - * * All newly-reserved resources are available for serving the instance's - * tables. - * * The instance's new resource levels are readable via the API. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format `/operations/` and - * can be used to track the instance modification. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [UpdateInstanceMetadata][google.spanner.admin.instance.v1.UpdateInstanceMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [Instance][google.spanner.admin.instance.v1.Instance], if successful. - * - * Authorization requires `spanner.instances.update` permission on - * the resource [name][google.spanner.admin.instance.v1.Instance.name]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $instance = new Instance(); - * $fieldMask = new FieldMask(); - * $operationResponse = $instanceAdminClient->updateInstance($instance, $fieldMask); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->updateInstance($instance, $fieldMask); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'updateInstance'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param Instance $instance Required. The instance to update, which must always include the instance - * name. Otherwise, only fields mentioned in - * [field_mask][google.spanner.admin.instance.v1.UpdateInstanceRequest.field_mask] - * need be included. - * @param FieldMask $fieldMask Required. A mask specifying which fields in - * [Instance][google.spanner.admin.instance.v1.Instance] should be updated. - * The field mask must always be specified; this prevents any future fields in - * [Instance][google.spanner.admin.instance.v1.Instance] from being erased - * accidentally by clients that do not know about them. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function updateInstance( - $instance, - $fieldMask, - array $optionalArgs = [] - ) { - $request = new UpdateInstanceRequest(); - $requestParamHeaders = []; - $request->setInstance($instance); - $request->setFieldMask($fieldMask); - $requestParamHeaders['instance.name'] = $instance->getName(); - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'UpdateInstance', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Updates an instance configuration. The returned - * [long-running operation][google.longrunning.Operation] can be used to track - * the progress of updating the instance. If the named instance configuration - * does not exist, returns `NOT_FOUND`. - * - * Only user-managed configurations can be updated. - * - * Immediately after the request returns: - * - * * The instance configuration's - * [reconciling][google.spanner.admin.instance.v1.InstanceConfig.reconciling] - * field is set to true. - * - * While the operation is pending: - * - * * Cancelling the operation sets its metadata's - * [cancel_time][google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata.cancel_time]. - * The operation is guaranteed to succeed at undoing all changes, after - * which point it terminates with a `CANCELLED` status. - * * All other attempts to modify the instance configuration are rejected. - * * Reading the instance configuration via the API continues to give the - * pre-request values. - * - * Upon completion of the returned operation: - * - * * Creating instances using the instance configuration uses the new - * values. - * * The new values of the instance configuration are readable via the API. - * * The instance configuration's - * [reconciling][google.spanner.admin.instance.v1.InstanceConfig.reconciling] - * field becomes false. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format - * `/operations/` and can be used to track - * the instance configuration modification. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [UpdateInstanceConfigMetadata][google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig], if - * successful. - * - * Authorization requires `spanner.instanceConfigs.update` permission on - * the resource [name][google.spanner.admin.instance.v1.InstanceConfig.name]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $instanceConfig = new InstanceConfig(); - * $updateMask = new FieldMask(); - * $operationResponse = $instanceAdminClient->updateInstanceConfig($instanceConfig, $updateMask); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->updateInstanceConfig($instanceConfig, $updateMask); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'updateInstanceConfig'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param InstanceConfig $instanceConfig Required. The user instance configuration to update, which must always - * include the instance configuration name. Otherwise, only fields mentioned - * in - * [update_mask][google.spanner.admin.instance.v1.UpdateInstanceConfigRequest.update_mask] - * need be included. To prevent conflicts of concurrent updates, - * [etag][google.spanner.admin.instance.v1.InstanceConfig.reconciling] can - * be used. - * @param FieldMask $updateMask Required. A mask specifying which fields in - * [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig] should be - * updated. The field mask must always be specified; this prevents any future - * fields in [InstanceConfig][google.spanner.admin.instance.v1.InstanceConfig] - * from being erased accidentally by clients that do not know about them. Only - * display_name and labels can be updated. - * @param array $optionalArgs { - * Optional. - * - * @type bool $validateOnly - * An option to validate, but not actually execute, a request, - * and provide the same response. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function updateInstanceConfig( - $instanceConfig, - $updateMask, - array $optionalArgs = [] - ) { - $request = new UpdateInstanceConfigRequest(); - $requestParamHeaders = []; - $request->setInstanceConfig($instanceConfig); - $request->setUpdateMask($updateMask); - $requestParamHeaders[ - 'instance_config.name' - ] = $instanceConfig->getName(); - if (isset($optionalArgs['validateOnly'])) { - $request->setValidateOnly($optionalArgs['validateOnly']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'UpdateInstanceConfig', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } - - /** - * Updates an instance partition, and begins allocating or releasing resources - * as requested. The returned [long-running - * operation][google.longrunning.Operation] can be used to track the - * progress of updating the instance partition. If the named instance - * partition does not exist, returns `NOT_FOUND`. - * - * Immediately upon completion of this request: - * - * * For resource types for which a decrease in the instance partition's - * allocation has been requested, billing is based on the newly-requested - * level. - * - * Until completion of the returned operation: - * - * * Cancelling the operation sets its metadata's - * [cancel_time][google.spanner.admin.instance.v1.UpdateInstancePartitionMetadata.cancel_time], - * and begins restoring resources to their pre-request values. The - * operation is guaranteed to succeed at undoing all resource changes, - * after which point it terminates with a `CANCELLED` status. - * * All other attempts to modify the instance partition are rejected. - * * Reading the instance partition via the API continues to give the - * pre-request resource levels. - * - * Upon completion of the returned operation: - * - * * Billing begins for all successfully-allocated resources (some types - * may have lower than the requested levels). - * * All newly-reserved resources are available for serving the instance - * partition's tables. - * * The instance partition's new resource levels are readable via the API. - * - * The returned [long-running operation][google.longrunning.Operation] will - * have a name of the format - * `/operations/` and can be used to - * track the instance partition modification. The - * [metadata][google.longrunning.Operation.metadata] field type is - * [UpdateInstancePartitionMetadata][google.spanner.admin.instance.v1.UpdateInstancePartitionMetadata]. - * The [response][google.longrunning.Operation.response] field type is - * [InstancePartition][google.spanner.admin.instance.v1.InstancePartition], if - * successful. - * - * Authorization requires `spanner.instancePartitions.update` permission on - * the resource - * [name][google.spanner.admin.instance.v1.InstancePartition.name]. - * - * Sample code: - * ``` - * $instanceAdminClient = new InstanceAdminClient(); - * try { - * $instancePartition = new InstancePartition(); - * $fieldMask = new FieldMask(); - * $operationResponse = $instanceAdminClient->updateInstancePartition($instancePartition, $fieldMask); - * $operationResponse->pollUntilComplete(); - * if ($operationResponse->operationSucceeded()) { - * $result = $operationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $operationResponse->getError(); - * // handleError($error) - * } - * // Alternatively: - * // start the operation, keep the operation name, and resume later - * $operationResponse = $instanceAdminClient->updateInstancePartition($instancePartition, $fieldMask); - * $operationName = $operationResponse->getName(); - * // ... do other work - * $newOperationResponse = $instanceAdminClient->resumeOperation($operationName, 'updateInstancePartition'); - * while (!$newOperationResponse->isDone()) { - * // ... do other work - * $newOperationResponse->reload(); - * } - * if ($newOperationResponse->operationSucceeded()) { - * $result = $newOperationResponse->getResult(); - * // doSomethingWith($result) - * } else { - * $error = $newOperationResponse->getError(); - * // handleError($error) - * } - * } finally { - * $instanceAdminClient->close(); - * } - * ``` - * - * @param InstancePartition $instancePartition Required. The instance partition to update, which must always include the - * instance partition name. Otherwise, only fields mentioned in - * [field_mask][google.spanner.admin.instance.v1.UpdateInstancePartitionRequest.field_mask] - * need be included. - * @param FieldMask $fieldMask Required. A mask specifying which fields in - * [InstancePartition][google.spanner.admin.instance.v1.InstancePartition] - * should be updated. The field mask must always be specified; this prevents - * any future fields in - * [InstancePartition][google.spanner.admin.instance.v1.InstancePartition] - * from being erased accidentally by clients that do not know about them. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\OperationResponse - * - * @throws ApiException if the remote call fails - */ - public function updateInstancePartition( - $instancePartition, - $fieldMask, - array $optionalArgs = [] - ) { - $request = new UpdateInstancePartitionRequest(); - $requestParamHeaders = []; - $request->setInstancePartition($instancePartition); - $request->setFieldMask($fieldMask); - $requestParamHeaders[ - 'instance_partition.name' - ] = $instancePartition->getName(); - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startOperationsCall( - 'UpdateInstancePartition', - $optionalArgs, - $request, - $this->getOperationsClient() - )->wait(); - } -} diff --git a/Spanner/src/ArrayType.php b/Spanner/src/ArrayType.php index 518db7088370..7b8183b22feb 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); @@ -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 8f54a6297ba9..5cbc27b8faed 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,39 @@ 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(), + public function create( + $database, + DateTimeInterface $expireTime, + array $options = [] + ): OperationResponse { + [$data, $callOptions] = $this->splitOptionalArgs($options); + + $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->formatTimeAsArray($expireTime), ], - ] + $options); + ]; - return $this->resumeOperation($operation['name'], $operation); + 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); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->instance->name()); + + return $this->databaseAdminClient->createBackup($request, $callOptions) + ->withResultFunction($this->backupResultFunction()); } /** @@ -209,24 +153,28 @@ 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(), + 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'], - 'sourceBackupId' => $this->fullyQualifiedBackupName($this->name), - 'expireTime' => $expireTime->format('Y-m-d\TH:i:s.u\Z') - ] + $options); + 'sourceBackup' => $this->fullyQualifiedBackupName($this->name), + 'expireTime' => $this->formatTimeAsArray($expireTime) + ]; - 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()); } /** @@ -240,9 +188,17 @@ 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 { - 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); } /** @@ -260,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); @@ -282,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); @@ -300,7 +256,7 @@ public function info(array $options = []) * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -316,11 +272,18 @@ public function name() * @param array $options [optional] Configuration options. * @return array */ - public function reload(array $options = []) + public function reload(array $options = []): array { - 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); } /** @@ -344,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); @@ -364,19 +327,81 @@ 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 { - 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->formatTimeAsArray($newTimestamp), ], '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 = []): OperationResponse + { + 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 ItemIterator + */ + public function longRunningOperations(array $options = []): ItemIterator + { + [$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 + ); } /** @@ -384,7 +409,7 @@ public function updateExpireTime(DateTimeInterface $newTimestamp, array $options * * @return string */ - private function fullyQualifiedBackupName($name) + private function fullyQualifiedBackupName($name): string { $instance = DatabaseAdminClient::parseName($this->instance->name())['instance']; @@ -400,4 +425,13 @@ private function fullyQualifiedBackupName($name) } //@codeCoverageIgnoreEnd } + + 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..3b4350772c20 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) { @@ -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 0fc38020c079..68657309aa57 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'); * ``` @@ -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/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..3fe0fb3ed557 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,10 +294,13 @@ 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 +315,7 @@ public function createBackup($name, \DateTimeInterface $expireTime, array $optio * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -358,7 +335,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 +355,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 +384,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 +412,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 +446,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 +471,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 +520,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 +555,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 + ]; + + $request = $this->serializer->decodeMessage(new UpdateDatabaseDdlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); - return $this->resumeOperation($operation['name'], $operation); + return $this->databaseAdminClient->updateDatabaseDdl($request, $callOptions); } /** @@ -598,11 +593,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 +630,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 +656,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 +729,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 +746,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 +800,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 +886,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 +913,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 +986,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 +1033,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 +1082,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 +1128,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 +1174,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 +1221,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 +1269,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 +1316,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 +1364,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 +1414,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 +1673,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 +1694,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 +1707,7 @@ public function execute($sql, array $options = []) * * @return MutationGroup */ - public function mutationGroup() + public function mutationGroup(): MutationGroup { return new MutationGroup($this->returnInt64AsObject); } @@ -1746,11 +1754,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 +1772,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 +1913,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 +1926,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 +2058,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 +2078,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 +2096,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 +2114,7 @@ public function sessionPool() * $database->close(); * ``` */ - public function close() + public function close(): void { if ($this->session) { if ($this->sessionPool) { @@ -2113,7 +2136,8 @@ public function __destruct() $this->close(); //@codingStandardsIgnoreStart //@codeCoverageIgnoreStart - } catch (\Exception $ex) {} + } catch (\Exception $ex) { + } //@codeCoverageIgnoreEnd //@codingStandardsIgnoreStart } @@ -2128,7 +2152,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 +2169,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 +2180,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 +2193,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 = $database->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 = $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 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 +2395,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 +2428,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 +2445,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 +2465,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 +2478,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..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). @@ -25,7 +27,7 @@ * ``` * use Google\Cloud\Spanner\SpannerClient; * - * $spanner = new SpannerClient(); + * $spanner = new SpannerClient(['projectId' => 'my-project']); * * $date = $spanner->date(new \DateTimeImmutable('1995-02-04')); * ``` @@ -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/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..276c29409e1a 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); } /** @@ -181,7 +141,7 @@ public function __construct( * * @return string */ - public function name() + public function name(): string { return $this->name; } @@ -208,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); @@ -232,17 +192,19 @@ 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 { 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); } @@ -274,14 +236,30 @@ public function exists(array $options = []) * } * @return array */ - public function reload(array $options = []) + public function reload(array $options = []): array { - $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 = []) + public function create(InstanceConfiguration $config, array $options = []): OperationResponse { + 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) + ]; - return $this->resumeOperation($operation['name'], $operation); + $request = $this->serializer->decodeMessage(new CreateInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->instanceAdminClient->createInstance($request, $callOptions) + ->withResultFunction($this->instanceResultFunction()); } /** @@ -356,11 +332,12 @@ 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); - 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 = []) + public function update(array $options = []): OperationResponse { + 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) + ]; - return $this->resumeOperation($operation['name'], $operation); + $request = $this->serializer->decodeMessage(new UpdateInstanceRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->name); + + return $this->instanceAdminClient->updateInstance($request, $callOptions) + ->withResultFunction($this->instanceResultFunction()); } /** @@ -422,11 +407,15 @@ public function update(array $options = []) * @param array $options [optional] Configuration options. * @return void */ - public function delete(array $options = []) + public function delete(array $options = []): void { - $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,9 +438,9 @@ public function delete(array $options = []) * @type SessionPoolInterface $sessionPool A pool used to manage * sessions. * } - * @return LongRunningOperation + * @return OperationResponse */ - public function createDatabase($name, array $options = []) + public function createDatabase($name, array $options = []): OperationResponse { $instantiation = $this->pluckArray(['sessionPool'], $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 = []) + public function createDatabaseFromBackup($name, $backup, array $options = []): OperationResponse { - $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); } /** @@ -515,19 +493,23 @@ 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->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, + ] ); } @@ -555,21 +537,23 @@ public function database($name, array $options = []) * } * @return ItemIterator */ - public function databases(array $options = []) + public function databases(array $options = []): ItemIterator { - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $database) { - return $this->database($database['name'], ['database' => $database]); - }, - [$this->connection, 'listDatabases'], - $options + ['instance' => $this->name], - [ - 'itemsKey' => 'databases', - 'resultLimit' => $resultLimit - ] - ) + [$data, $callOptions] = $this->splitOptionalArgs($options); + $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, + function (array $database) { + return $this->database($database['name'], ['database' => $database]); + }, + 'databases', + $this->pluck('resultLimit', $options, false) ); } @@ -585,13 +569,12 @@ function (array $database) { * * @return Backup */ - public function backup($name, array $backup = []) + public function backup($name, array $backup = []): Backup { return new Backup( - $this->connection, + $this->databaseAdminClient, + $this->serializer, $this, - $this->lroConnection, - $this->lroCallables, $this->projectId, $name, $backup @@ -629,24 +612,23 @@ public function backup($name, array $backup = []) * * @return ItemIterator */ - public function backups(array $options = []) + public function backups(array $options = []): ItemIterator { - $resultLimit = $this->pluck('resultLimit', $options, false); - return new ItemIterator( - new PageIterator( - function (array $backup) { - return $this->backup( - $backup['name'], - $backup - ); - }, - [$this->connection, 'listBackups'], - $options + ['instance' => $this->name], - [ - 'itemsKey' => 'backups', - 'resultLimit' => $resultLimit - ] - ) + [$data, $callOptions] = $this->splitOptionalArgs($options); + $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, + function (array $backup) { + return $this->backup($backup['name'], $backup); + }, + 'backups', + $this->pluck('resultLimit', $options, false) ); } @@ -674,24 +656,11 @@ function (array $backup) { * been generated by a previous call to the API. * } * - * @return ItemIterator + * @return ItemIterator */ - public function backupOperations(array $options = []) + public function backupOperations(array $options = []): ItemIterator { - $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 +687,11 @@ function (array $operation) { * been generated by a previous call to the API. * } * - * @return ItemIterator + * @return ItemIterator */ - public function databaseOperations(array $options = []) + public function databaseOperations(array $options = []): ItemIterator { - $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 +702,15 @@ function (array $operation) { * $iam = $instance->iam(); * ``` * - * @return Iam + * @return IamManager */ - public function iam() + public function iam(): IamManager { 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 ); } @@ -767,31 +725,12 @@ public function iam() * @param string $project The project ID. * @return string */ - private function fullyQualifiedInstanceName($name, $project) + private function fullyQualifiedInstanceName($name, $project): string { - // 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 +742,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 @@ -820,8 +761,115 @@ public function __debugInfo() * * @return array */ - public function directedReadOptions() + public function directedReadOptions(): array { return $this->directedReadOptions; } + + /** + * @param array $instanceArray + * @return array + */ + private function fieldMask(array $instanceArray): array + { + $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 + ): array { + 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 = []): OperationResponse + { + 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 ItemIterator + */ + public function longRunningOperations(array $options = []): ItemIterator + { + [$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..488059eec401 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; /** - * @param ConnectionInterface $connection A connection to Google Cloud - * Spanner. This object is created by SpannerClient, - * and should not be instantiated outside of this client. + * @var array + */ + private $defaultQueryOptions; + + /** + * @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) ?: []; } /** @@ -99,7 +120,7 @@ public function __construct(ConnectionInterface $connection, $returnInt64AsObjec * } * @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]; } @@ -130,20 +151,31 @@ 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 { - $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); - $time = $this->parseTimeString($res['commitTimestamp']); - return [new Timestamp($time[0], $time[1]), $res]; + $response = $this->spannerClient->commit($request, $callOptions); + $timestamp = $response->getCommitTimestamp(); + + return [ + new Timestamp( + $this->createDateTimeFromSeconds($timestamp->getSeconds()), + $timestamp->getNanos() + ), + $this->handleResponse($response) + ]; } /** @@ -157,16 +189,23 @@ 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.'); } - $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); } /** @@ -192,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' => [], @@ -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) @@ -257,11 +296,13 @@ public function executeUpdate( Transaction $transaction, $sql, array $options = [] - ) { + ): int { 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]; } @@ -327,29 +368,20 @@ public function executeUpdateBatch( Transaction $transaction, array $statements, array $options = [] - ) { - $stmts = []; - foreach ($statements as $statement) { - if (!isset($statement['sql'])) { - throw new InvalidArgumentException('Each statement must contain a SQL key.'); - } + ): BatchDmlResult { + [$data, $callOptions] = $this->splitOptionalArgs($options); + $data['transaction'] = $this->createTransactionSelector($data, $transaction->id()); + $data += [ + 'session' => $session->name(), + 'statements' => $this->formatStatements($statements) + ]; - $parameters = $this->pluck('parameters', $statement, false) ?: []; - $types = $this->pluck('types', $statement, false) ?: []; - $stmts[] = [ - 'sql' => $statement['sql'] - ] + $this->mapper->formatParamsForExecuteSql($parameters, $types); - } + $request = $this->serializer->decodeMessage(new ExecuteBatchDmlRequest(), $data); + $callOptions = $this->addResourcePrefixHeader($callOptions, $this->getDatabaseNameFromSession($session)); + $callOptions = $this->addLarHeader($callOptions, $this->routeToLeader); - if (!isset($options['transaction']['begin'])) { - $options['transaction'] = ['id' => $transaction->id()]; - } - - $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. @@ -401,15 +433,8 @@ public function read( KeySet $keySet, array $columns, array $options = [] - ) { - $options += [ - 'index' => null, - 'limit' => null, - 'offset' => null, - 'transactionContext' => null - ]; - - $context = $this->pluck('transactionContext', $options); + ): Result { + $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, @@ -458,14 +483,15 @@ public function read( * } * @return Transaction */ - public function transaction(Session $session, array $options = []) + public function transaction(Session $session, array $options = []): Transaction { $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 ] ); @@ -499,14 +529,17 @@ 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 ]; $options += [ 'tag' => null, - 'transactionOptions' => null + 'transactionOptions' => [] ]; $options['isRetry'] = $options['isRetry'] ?? false; @@ -538,27 +571,27 @@ 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 */ - public function snapshot(Session $session, array $options = []) + public function snapshot(Session $session, array $options = []): TransactionalReadInterface { $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, @@ -575,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 @@ -615,15 +651,23 @@ public function createSnapshot(Session $session, array $res = [], $className = S * } * @return Session */ - public function createSession($databaseName, array $options = []) + public function createSession($databaseName, array $options = []): Session { - $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']); } @@ -639,15 +683,17 @@ 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 = 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] ); } @@ -688,23 +734,30 @@ 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); - $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) { @@ -749,20 +802,26 @@ public function partitionRead( KeySet $keySet, array $columns, array $options = [] - ) { + ): array { // 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 +843,12 @@ public function partitionRead( * @param array $options * @return array */ - private function partitionOptions(array $options) + private function partitionOptions(array &$options): array { - $options['partitionOptions'] = array_filter([ + return array_filter([ 'partitionSizeBytes' => $this->pluck('partitionSizeBytes', $options, false), 'maxPartitions' => $this->pluck('maxPartitions', $options, false) ]); - - return $options; } /** @@ -804,16 +861,26 @@ private function partitionOptions(array $options) * * @return array */ - private function beginTransaction(Session $session, array $options = []) + private function beginTransaction(Session $session, array $options = []): array { - $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); } /** @@ -822,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(); @@ -843,11 +910,247 @@ private function flattenKeySet(KeySet $keySet) return $this->arrayFilterRemoveNull($keys); } - private function getDatabaseNameFromSession(Session $session) + + private function getDatabaseNameFromSession(Session $session): string { return $session->info()['databaseName']; } + /** + * Serialize the mutations. + * + * @param array $mutations + * @return array + */ + private function serializeMutations(array $mutations): array + { + $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): array + { + $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): array + { + $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): array + { + $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): array + { + $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): array + { + 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): \Generator + { + list($data, $callOptions) = $this->splitOptionalArgs($args); + $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): array + { + // 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): 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; + } + + /** + * 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. * @@ -857,7 +1160,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 $resultMapper [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 $resultMapper = null + ): ItemIterator { + $resultLimit = $this->pluck('resultLimit', $callOptions, false) ?: 0; + return new ItemIterator( + new PageIterator( + $resultMapper ?: 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 + ] + ) + ); + } + + 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/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..6143f03da4ec 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 Connection\ConnectionInterface - * @internal + * @var string */ - protected $connection; + private $projectId; + + /** + * @var string + */ + 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,20 +466,21 @@ public function createInstanceConfiguration(InstanceConfiguration $baseConfig, $ */ public function instanceConfigurations(array $options = []) { - $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, - [ - 'itemsKey' => 'instanceConfigs', - 'resultLimit' => $resultLimit - ] - ) + [$data, $callOptions] = $this->splitOptionalArgs($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, + function (array $config) { + return $this->instanceConfiguration($config['name'], $config); + }, + 'instanceConfigs', + $this->pluck('resultLimit', $options, false) ); } @@ -456,11 +509,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 +541,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 +587,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 +610,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,24 +654,22 @@ public function instance($name, array $instance = []) */ public function instances(array $options = []) { - $options += [ - 'filter' => null - ]; - - $resultLimit = $this->pluck('resultLimit', $options, 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, - [ - 'itemsKey' => 'instances', - 'resultLimit' => $resultLimit - ] - ) + [$data, $callOptions] = $this->splitOptionalArgs($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, + function (array $instance) { + $name = InstanceAdminClient::parseName($instance['name'])['instance']; + return $this->instance($name, $instance); + }, + 'instances', + $this->pluck('resultLimit', $options, false) ); } @@ -650,6 +711,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 +951,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 +970,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/V1/Gapic/SpannerGapicClient.php b/Spanner/src/V1/Gapic/SpannerGapicClient.php deleted file mode 100644 index fc2159eb9d3e..000000000000 --- a/Spanner/src/V1/Gapic/SpannerGapicClient.php +++ /dev/null @@ -1,2081 +0,0 @@ -databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $sessionCount = 0; - * $response = $spannerClient->batchCreateSessions($formattedDatabase, $sessionCount); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * Many parameters require resource names to be formatted in a particular way. To - * assist with these names, this class includes a format method for each type of - * name, and additionally a parseName method to extract the individual identifiers - * contained within formatted names that are returned by the API. - * - * @deprecated Please use the new service client {@see \Google\Cloud\Spanner\V1\Client\SpannerClient}. - */ -class SpannerGapicClient -{ - use GapicClientTrait; - - /** The name of the service. */ - const SERVICE_NAME = 'google.spanner.v1.Spanner'; - - /** - * The default address of the service. - * - * @deprecated SERVICE_ADDRESS_TEMPLATE should be used instead. - */ - const SERVICE_ADDRESS = 'spanner.googleapis.com'; - - /** The address template of the service. */ - private const SERVICE_ADDRESS_TEMPLATE = 'spanner.UNIVERSE_DOMAIN'; - - /** The default port of the service. */ - const DEFAULT_SERVICE_PORT = 443; - - /** The name of the code generator, to be included in the agent header. */ - const CODEGEN_NAME = 'gapic'; - - /** The default scopes required by the service. */ - public static $serviceScopes = [ - 'https://www.googleapis.com/auth/cloud-platform', - 'https://www.googleapis.com/auth/spanner.data', - ]; - - private static $databaseNameTemplate; - - private static $sessionNameTemplate; - - private static $pathTemplateMap; - - private static function getClientDefaults() - { - return [ - 'serviceName' => self::SERVICE_NAME, - 'apiEndpoint' => - self::SERVICE_ADDRESS . ':' . self::DEFAULT_SERVICE_PORT, - 'clientConfig' => - __DIR__ . '/../resources/spanner_client_config.json', - 'descriptorsConfigPath' => - __DIR__ . '/../resources/spanner_descriptor_config.php', - 'gcpApiConfigPath' => - __DIR__ . '/../resources/spanner_grpc_config.json', - 'credentialsConfig' => [ - 'defaultScopes' => self::$serviceScopes, - ], - 'transportConfig' => [ - 'rest' => [ - 'restClientConfigPath' => - __DIR__ . - '/../resources/spanner_rest_client_config.php', - ], - ], - ]; - } - - private static function getDatabaseNameTemplate() - { - if (self::$databaseNameTemplate == null) { - self::$databaseNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/databases/{database}' - ); - } - - return self::$databaseNameTemplate; - } - - private static function getSessionNameTemplate() - { - if (self::$sessionNameTemplate == null) { - self::$sessionNameTemplate = new PathTemplate( - 'projects/{project}/instances/{instance}/databases/{database}/sessions/{session}' - ); - } - - return self::$sessionNameTemplate; - } - - private static function getPathTemplateMap() - { - if (self::$pathTemplateMap == null) { - self::$pathTemplateMap = [ - 'database' => self::getDatabaseNameTemplate(), - 'session' => self::getSessionNameTemplate(), - ]; - } - - return self::$pathTemplateMap; - } - - /** - * Formats a string containing the fully-qualified path to represent a database - * resource. - * - * @param string $project - * @param string $instance - * @param string $database - * - * @return string The formatted database resource. - */ - public static function databaseName($project, $instance, $database) - { - return self::getDatabaseNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'database' => $database, - ]); - } - - /** - * Formats a string containing the fully-qualified path to represent a session - * resource. - * - * @param string $project - * @param string $instance - * @param string $database - * @param string $session - * - * @return string The formatted session resource. - */ - public static function sessionName($project, $instance, $database, $session) - { - return self::getSessionNameTemplate()->render([ - 'project' => $project, - 'instance' => $instance, - 'database' => $database, - 'session' => $session, - ]); - } - - /** - * Parses a formatted name string and returns an associative array of the components in the name. - * The following name formats are supported: - * Template: Pattern - * - database: projects/{project}/instances/{instance}/databases/{database} - * - session: projects/{project}/instances/{instance}/databases/{database}/sessions/{session} - * - * The optional $template argument can be supplied to specify a particular pattern, - * and must match one of the templates listed above. If no $template argument is - * provided, or if the $template argument does not match one of the templates - * listed, then parseName will check each of the supported templates, and return - * the first match. - * - * @param string $formattedName The formatted name string - * @param string $template Optional name of template to match - * - * @return array An associative array from name component IDs to component values. - * - * @throws ValidationException If $formattedName could not be matched. - */ - public static function parseName($formattedName, $template = null) - { - $templateMap = self::getPathTemplateMap(); - if ($template) { - if (!isset($templateMap[$template])) { - throw new ValidationException( - "Template name $template does not exist" - ); - } - - return $templateMap[$template]->match($formattedName); - } - - foreach ($templateMap as $templateName => $pathTemplate) { - try { - return $pathTemplate->match($formattedName); - } catch (ValidationException $ex) { - // Swallow the exception to continue trying other path templates - } - } - - throw new ValidationException( - "Input did not match any known format. Input: $formattedName" - ); - } - - /** - * Constructor. - * - * @param array $options { - * Optional. Options for configuring the service API wrapper. - * - * @type string $apiEndpoint - * The address of the API remote host. May optionally include the port, formatted - * as ":". Default 'spanner.googleapis.com:443'. - * @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials - * The credentials to be used by the client to authorize API calls. This option - * accepts either a path to a credentials file, or a decoded credentials file as a - * PHP array. - * *Advanced usage*: In addition, this option can also accept a pre-constructed - * {@see \Google\Auth\FetchAuthTokenInterface} object or - * {@see \Google\ApiCore\CredentialsWrapper} object. Note that when one of these - * objects are provided, any settings in $credentialsConfig will be ignored. - * @type array $credentialsConfig - * Options used to configure credentials, including auth token caching, for the - * client. For a full list of supporting configuration options, see - * {@see \Google\ApiCore\CredentialsWrapper::build()} . - * @type bool $disableRetries - * Determines whether or not retries defined by the client configuration should be - * disabled. Defaults to `false`. - * @type string|array $clientConfig - * Client method configuration, including retry settings. This option can be either - * a path to a JSON file, or a PHP array containing the decoded JSON data. By - * default this settings points to the default client config file, which is - * provided in the resources folder. - * @type string|TransportInterface $transport - * The transport used for executing network requests. May be either the string - * `rest` or `grpc`. Defaults to `grpc` if gRPC support is detected on the system. - * *Advanced usage*: Additionally, it is possible to pass in an already - * instantiated {@see \Google\ApiCore\Transport\TransportInterface} object. Note - * that when this object is provided, any settings in $transportConfig, and any - * $apiEndpoint setting, will be ignored. - * @type array $transportConfig - * Configuration options that will be used to construct the transport. Options for - * each supported transport type should be passed in a key for that transport. For - * example: - * $transportConfig = [ - * 'grpc' => [...], - * 'rest' => [...], - * ]; - * See the {@see \Google\ApiCore\Transport\GrpcTransport::build()} and - * {@see \Google\ApiCore\Transport\RestTransport::build()} methods for the - * supported options. - * @type callable $clientCertSource - * A callable which returns the client cert as a string. This can be used to - * provide a certificate and private key to the transport layer for mTLS. - * } - * - * @throws ValidationException - */ - public function __construct(array $options = []) - { - $clientOptions = $this->buildClientOptions($options); - $this->setClientOptions($clientOptions); - } - - /** - * Creates multiple new sessions. - * - * This API can be used to initialize a session cache on the clients. - * See https://goo.gl/TgSFN2 for best practices on session cache management. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedDatabase = $spannerClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $sessionCount = 0; - * $response = $spannerClient->batchCreateSessions($formattedDatabase, $sessionCount); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $database Required. The database in which the new sessions are created. - * @param int $sessionCount Required. The number of sessions to be created in this batch call. - * The API may return fewer than the requested number of sessions. If a - * specific number of sessions are desired, the client can make additional - * calls to BatchCreateSessions (adjusting - * [session_count][google.spanner.v1.BatchCreateSessionsRequest.session_count] - * as necessary). - * @param array $optionalArgs { - * Optional. - * - * @type Session $sessionTemplate - * Parameters to be applied to each created session. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\BatchCreateSessionsResponse - * - * @throws ApiException if the remote call fails - */ - public function batchCreateSessions( - $database, - $sessionCount, - array $optionalArgs = [] - ) { - $request = new BatchCreateSessionsRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $request->setSessionCount($sessionCount); - $requestParamHeaders['database'] = $database; - if (isset($optionalArgs['sessionTemplate'])) { - $request->setSessionTemplate($optionalArgs['sessionTemplate']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'BatchCreateSessions', - BatchCreateSessionsResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Batches the supplied mutation groups in a collection of efficient - * transactions. All mutations in a group are committed atomically. However, - * mutations across groups can be committed non-atomically in an unspecified - * order and thus, they must be independent of each other. Partial failure is - * possible, i.e., some groups may have been committed successfully, while - * some may have failed. The results of individual batches are streamed into - * the response as the batches are applied. - * - * BatchWrite requests are not replay protected, meaning that each mutation - * group may be applied more than once. Replays of non-idempotent mutations - * may have undesirable effects. For example, replays of an insert mutation - * may produce an already exists error or if you use generated or commit - * timestamp-based keys, it may result in additional rows being added to the - * mutation's table. We recommend structuring your mutation groups to be - * idempotent to avoid this issue. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $mutationGroups = []; - * // Read all responses until the stream is complete - * $stream = $spannerClient->batchWrite($formattedSession, $mutationGroups); - * foreach ($stream->readAll() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the batch request is to be run. - * @param MutationGroup[] $mutationGroups Required. The groups of mutations to be applied. - * @param array $optionalArgs { - * Optional. - * - * @type RequestOptions $requestOptions - * Common options for this request. - * @type bool $excludeTxnFromChangeStreams - * Optional. When `exclude_txn_from_change_streams` is set to `true`: - * * Mutations from all transactions in this batch write operation will not - * be recorded in change streams with DDL option `allow_txn_exclusion=true` - * that are tracking columns modified by these transactions. - * * Mutations from all transactions in this batch write operation will be - * recorded in change streams with DDL option `allow_txn_exclusion=false or - * not set` that are tracking columns modified by these transactions. - * - * When `exclude_txn_from_change_streams` is set to `false` or not set, - * mutations from all transactions in this batch write operation will be - * recorded in all change streams that are tracking columns modified by these - * transactions. - * @type int $timeoutMillis - * Timeout to use for this call. - * } - * - * @return \Google\ApiCore\ServerStream - * - * @throws ApiException if the remote call fails - */ - public function batchWrite( - $session, - $mutationGroups, - array $optionalArgs = [] - ) { - $request = new BatchWriteRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setMutationGroups($mutationGroups); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['excludeTxnFromChangeStreams'])) { - $request->setExcludeTxnFromChangeStreams( - $optionalArgs['excludeTxnFromChangeStreams'] - ); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'BatchWrite', - BatchWriteResponse::class, - $optionalArgs, - $request, - Call::SERVER_STREAMING_CALL - ); - } - - /** - * Begins a new transaction. This step can often be skipped: - * [Read][google.spanner.v1.Spanner.Read], - * [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] and - * [Commit][google.spanner.v1.Spanner.Commit] can begin a new transaction as a - * side-effect. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $options = new TransactionOptions(); - * $response = $spannerClient->beginTransaction($formattedSession, $options); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the transaction runs. - * @param TransactionOptions $options Required. Options for the new transaction. - * @param array $optionalArgs { - * Optional. - * - * @type RequestOptions $requestOptions - * Common options for this request. - * Priority is ignored for this request. Setting the priority in this - * request_options struct will not do anything. To set the priority for a - * transaction, set it on the reads and writes that are part of this - * transaction instead. - * @type Mutation $mutationKey - * Optional. Required for read-write transactions on a multiplexed session - * that commit mutations but do not perform any reads or queries. Clients - * should randomly select one of the mutations from the mutation set and send - * it as a part of this request. - * This feature is not yet supported and will result in an UNIMPLEMENTED - * error. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\Transaction - * - * @throws ApiException if the remote call fails - */ - public function beginTransaction( - $session, - $options, - array $optionalArgs = [] - ) { - $request = new BeginTransactionRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setOptions($options); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['mutationKey'])) { - $request->setMutationKey($optionalArgs['mutationKey']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'BeginTransaction', - Transaction::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Commits a transaction. The request includes the mutations to be - * applied to rows in the database. - * - * `Commit` might return an `ABORTED` error. This can occur at any time; - * commonly, the cause is conflicts with concurrent - * transactions. However, it can also happen for a variety of other - * reasons. If `Commit` returns `ABORTED`, the caller should re-attempt - * the transaction from the beginning, re-using the same session. - * - * On very rare occasions, `Commit` might return `UNKNOWN`. This can happen, - * for example, if the client job experiences a 1+ hour networking failure. - * At that point, Cloud Spanner has lost track of the transaction outcome and - * we recommend that you perform another read from the database to see the - * state of things as they are now. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $mutations = []; - * $response = $spannerClient->commit($formattedSession, $mutations); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the transaction to be committed is running. - * @param Mutation[] $mutations The mutations to be executed when this transaction commits. All - * mutations are applied atomically, in the order they appear in - * this list. - * @param array $optionalArgs { - * Optional. - * - * @type string $transactionId - * Commit a previously-started transaction. - * @type TransactionOptions $singleUseTransaction - * Execute mutations in a temporary transaction. Note that unlike - * commit of a previously-started transaction, commit with a - * temporary transaction is non-idempotent. That is, if the - * `CommitRequest` is sent to Cloud Spanner more than once (for - * instance, due to retries in the application, or in the - * transport library), it is possible that the mutations are - * executed more than once. If this is undesirable, use - * [BeginTransaction][google.spanner.v1.Spanner.BeginTransaction] and - * [Commit][google.spanner.v1.Spanner.Commit] instead. - * @type bool $returnCommitStats - * If `true`, then statistics related to the transaction will be included in - * the [CommitResponse][google.spanner.v1.CommitResponse.commit_stats]. - * Default value is `false`. - * @type Duration $maxCommitDelay - * Optional. The amount of latency this request is willing to incur in order - * to improve throughput. If this field is not set, Spanner assumes requests - * are relatively latency sensitive and automatically determines an - * appropriate delay time. You can specify a batching delay value between 0 - * and 500 ms. - * @type RequestOptions $requestOptions - * Common options for this request. - * @type MultiplexedSessionPrecommitToken $precommitToken - * Optional. If the read-write transaction was executed on a multiplexed - * session, the precommit token with the highest sequence number received in - * this transaction attempt, should be included here. Failing to do so will - * result in a FailedPrecondition error. - * This feature is not yet supported and will result in an UNIMPLEMENTED - * error. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\CommitResponse - * - * @throws ApiException if the remote call fails - */ - public function commit($session, $mutations, array $optionalArgs = []) - { - $request = new CommitRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setMutations($mutations); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transactionId'])) { - $request->setTransactionId($optionalArgs['transactionId']); - } - - if (isset($optionalArgs['singleUseTransaction'])) { - $request->setSingleUseTransaction( - $optionalArgs['singleUseTransaction'] - ); - } - - if (isset($optionalArgs['returnCommitStats'])) { - $request->setReturnCommitStats($optionalArgs['returnCommitStats']); - } - - if (isset($optionalArgs['maxCommitDelay'])) { - $request->setMaxCommitDelay($optionalArgs['maxCommitDelay']); - } - - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['precommitToken'])) { - $request->setPrecommitToken($optionalArgs['precommitToken']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'Commit', - CommitResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Creates a new session. A session can be used to perform - * transactions that read and/or modify data in a Cloud Spanner database. - * Sessions are meant to be reused for many consecutive - * transactions. - * - * Sessions can only execute one transaction at a time. To execute - * multiple concurrent read-write/write-only transactions, create - * multiple sessions. Note that standalone reads and queries use a - * transaction internally, and count toward the one transaction - * limit. - * - * Active sessions use additional server resources, so it is a good idea to - * delete idle and unneeded sessions. - * Aside from explicit deletes, Cloud Spanner may delete sessions for which no - * operations are sent for more than an hour. If a session is deleted, - * requests to it return `NOT_FOUND`. - * - * Idle sessions can be kept alive by sending a trivial SQL query - * periodically, e.g., `"SELECT 1"`. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedDatabase = $spannerClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * $response = $spannerClient->createSession($formattedDatabase); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $database Required. The database in which the new session is created. - * @param array $optionalArgs { - * Optional. - * - * @type Session $session - * Required. The session to create. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\Session - * - * @throws ApiException if the remote call fails - */ - public function createSession($database, array $optionalArgs = []) - { - $request = new CreateSessionRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $requestParamHeaders['database'] = $database; - if (isset($optionalArgs['session'])) { - $request->setSession($optionalArgs['session']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'CreateSession', - Session::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Ends a session, releasing server resources associated with it. This will - * asynchronously trigger cancellation of any operations that are running with - * this session. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedName = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $spannerClient->deleteSession($formattedName); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the session to delete. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function deleteSession($name, array $optionalArgs = []) - { - $request = new DeleteSessionRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'DeleteSession', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Executes a batch of SQL DML statements. This method allows many statements - * to be run with lower latency than submitting them sequentially with - * [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. - * - * Statements are executed in sequential order. A request can succeed even if - * a statement fails. The - * [ExecuteBatchDmlResponse.status][google.spanner.v1.ExecuteBatchDmlResponse.status] - * field in the response provides information about the statement that failed. - * Clients must inspect this field to determine whether an error occurred. - * - * Execution stops after the first failed statement; the remaining statements - * are not executed. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $transaction = new TransactionSelector(); - * $statements = []; - * $seqno = 0; - * $response = $spannerClient->executeBatchDml($formattedSession, $transaction, $statements, $seqno); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the DML statements should be performed. - * @param TransactionSelector $transaction Required. The transaction to use. Must be a read-write transaction. - * - * To protect against replays, single-use transactions are not supported. The - * caller must either supply an existing transaction ID or begin a new - * transaction. - * @param Statement[] $statements Required. The list of statements to execute in this batch. Statements are - * executed serially, such that the effects of statement `i` are visible to - * statement `i+1`. Each statement must be a DML statement. Execution stops at - * the first failed statement; the remaining statements are not executed. - * - * Callers must provide at least one statement. - * @param int $seqno Required. A per-transaction sequence number used to identify this request. - * This field makes each request idempotent such that if the request is - * received multiple times, at most one will succeed. - * - * The sequence number must be monotonically increasing within the - * transaction. If a request arrives for the first time with an out-of-order - * sequence number, the transaction may be aborted. Replays of previously - * handled requests will yield the same response as the first execution. - * @param array $optionalArgs { - * Optional. - * - * @type RequestOptions $requestOptions - * Common options for this request. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\ExecuteBatchDmlResponse - * - * @throws ApiException if the remote call fails - */ - public function executeBatchDml( - $session, - $transaction, - $statements, - $seqno, - array $optionalArgs = [] - ) { - $request = new ExecuteBatchDmlRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setTransaction($transaction); - $request->setStatements($statements); - $request->setSeqno($seqno); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'ExecuteBatchDml', - ExecuteBatchDmlResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Executes an SQL statement, returning all results in a single reply. This - * method cannot be used to return a result set larger than 10 MiB; - * if the query yields more data than that, the query fails with - * a `FAILED_PRECONDITION` error. - * - * Operations inside read-write transactions might return `ABORTED`. If - * this occurs, the application should restart the transaction from - * the beginning. See [Transaction][google.spanner.v1.Transaction] for more - * details. - * - * Larger result sets can be fetched in streaming fashion by calling - * [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] - * instead. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $sql = 'sql'; - * $response = $spannerClient->executeSql($formattedSession, $sql); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the SQL query should be performed. - * @param string $sql Required. The SQL string. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * The transaction to use. - * - * For queries, if none is provided, the default is a temporary read-only - * transaction with strong concurrency. - * - * Standard DML statements require a read-write transaction. To protect - * against replays, single-use transactions are not supported. The caller - * must either supply an existing transaction ID or begin a new transaction. - * - * Partitioned DML requires an existing Partitioned DML transaction ID. - * @type Struct $params - * Parameter names and values that bind to placeholders in the SQL string. - * - * A parameter placeholder consists of the `@` character followed by the - * parameter name (for example, `@firstName`). Parameter names must conform - * to the naming requirements of identifiers as specified at - * https://cloud.google.com/spanner/docs/lexical#identifiers. - * - * Parameters can appear anywhere that a literal value is expected. The same - * parameter name can be used more than once, for example: - * - * `"WHERE id > @msg_id AND id < @msg_id + 100"` - * - * It is an error to execute a SQL statement with unbound parameters. - * @type array $paramTypes - * It is not always possible for Cloud Spanner to infer the right SQL type - * from a JSON value. For example, values of type `BYTES` and values - * of type `STRING` both appear in - * [params][google.spanner.v1.ExecuteSqlRequest.params] as JSON strings. - * - * In these cases, `param_types` can be used to specify the exact - * SQL type for some or all of the SQL statement parameters. See the - * definition of [Type][google.spanner.v1.Type] for more information - * about SQL types. - * @type string $resumeToken - * If this request is resuming a previously interrupted SQL statement - * execution, `resume_token` should be copied from the last - * [PartialResultSet][google.spanner.v1.PartialResultSet] yielded before the - * interruption. Doing this enables the new SQL statement execution to resume - * where the last one left off. The rest of the request parameters must - * exactly match the request that yielded this token. - * @type int $queryMode - * Used to control the amount of debugging information returned in - * [ResultSetStats][google.spanner.v1.ResultSetStats]. If - * [partition_token][google.spanner.v1.ExecuteSqlRequest.partition_token] is - * set, [query_mode][google.spanner.v1.ExecuteSqlRequest.query_mode] can only - * be set to - * [QueryMode.NORMAL][google.spanner.v1.ExecuteSqlRequest.QueryMode.NORMAL]. - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ExecuteSqlRequest\QueryMode} - * @type string $partitionToken - * If present, results will be restricted to the specified partition - * previously created using PartitionQuery(). There must be an exact - * match for the values of fields common to this message and the - * PartitionQueryRequest message used to create this partition_token. - * @type int $seqno - * A per-transaction sequence number used to identify this request. This field - * makes each request idempotent such that if the request is received multiple - * times, at most one will succeed. - * - * The sequence number must be monotonically increasing within the - * transaction. If a request arrives for the first time with an out-of-order - * sequence number, the transaction may be aborted. Replays of previously - * handled requests will yield the same response as the first execution. - * - * Required for DML statements. Ignored for queries. - * @type QueryOptions $queryOptions - * Query optimizer configuration to use for the given query. - * @type RequestOptions $requestOptions - * Common options for this request. - * @type DirectedReadOptions $directedReadOptions - * Directed read options for this request. - * @type bool $dataBoostEnabled - * If this is for a partitioned query and this field is set to `true`, the - * request is executed with Spanner Data Boost independent compute resources. - * - * If the field is set to `true` but the request does not set - * `partition_token`, the API returns an `INVALID_ARGUMENT` error. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\ResultSet - * - * @throws ApiException if the remote call fails - */ - public function executeSql($session, $sql, array $optionalArgs = []) - { - $request = new ExecuteSqlRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setSql($sql); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['params'])) { - $request->setParams($optionalArgs['params']); - } - - if (isset($optionalArgs['paramTypes'])) { - $request->setParamTypes($optionalArgs['paramTypes']); - } - - if (isset($optionalArgs['resumeToken'])) { - $request->setResumeToken($optionalArgs['resumeToken']); - } - - if (isset($optionalArgs['queryMode'])) { - $request->setQueryMode($optionalArgs['queryMode']); - } - - if (isset($optionalArgs['partitionToken'])) { - $request->setPartitionToken($optionalArgs['partitionToken']); - } - - if (isset($optionalArgs['seqno'])) { - $request->setSeqno($optionalArgs['seqno']); - } - - if (isset($optionalArgs['queryOptions'])) { - $request->setQueryOptions($optionalArgs['queryOptions']); - } - - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['directedReadOptions'])) { - $request->setDirectedReadOptions( - $optionalArgs['directedReadOptions'] - ); - } - - if (isset($optionalArgs['dataBoostEnabled'])) { - $request->setDataBoostEnabled($optionalArgs['dataBoostEnabled']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'ExecuteSql', - ResultSet::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Like [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql], except returns the - * result set as a stream. Unlike - * [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql], there is no limit on - * the size of the returned result set. However, no individual row in the - * result set can exceed 100 MiB, and no column value can exceed 10 MiB. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $sql = 'sql'; - * // Read all responses until the stream is complete - * $stream = $spannerClient->executeStreamingSql($formattedSession, $sql); - * foreach ($stream->readAll() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the SQL query should be performed. - * @param string $sql Required. The SQL string. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * The transaction to use. - * - * For queries, if none is provided, the default is a temporary read-only - * transaction with strong concurrency. - * - * Standard DML statements require a read-write transaction. To protect - * against replays, single-use transactions are not supported. The caller - * must either supply an existing transaction ID or begin a new transaction. - * - * Partitioned DML requires an existing Partitioned DML transaction ID. - * @type Struct $params - * Parameter names and values that bind to placeholders in the SQL string. - * - * A parameter placeholder consists of the `@` character followed by the - * parameter name (for example, `@firstName`). Parameter names must conform - * to the naming requirements of identifiers as specified at - * https://cloud.google.com/spanner/docs/lexical#identifiers. - * - * Parameters can appear anywhere that a literal value is expected. The same - * parameter name can be used more than once, for example: - * - * `"WHERE id > @msg_id AND id < @msg_id + 100"` - * - * It is an error to execute a SQL statement with unbound parameters. - * @type array $paramTypes - * It is not always possible for Cloud Spanner to infer the right SQL type - * from a JSON value. For example, values of type `BYTES` and values - * of type `STRING` both appear in - * [params][google.spanner.v1.ExecuteSqlRequest.params] as JSON strings. - * - * In these cases, `param_types` can be used to specify the exact - * SQL type for some or all of the SQL statement parameters. See the - * definition of [Type][google.spanner.v1.Type] for more information - * about SQL types. - * @type string $resumeToken - * If this request is resuming a previously interrupted SQL statement - * execution, `resume_token` should be copied from the last - * [PartialResultSet][google.spanner.v1.PartialResultSet] yielded before the - * interruption. Doing this enables the new SQL statement execution to resume - * where the last one left off. The rest of the request parameters must - * exactly match the request that yielded this token. - * @type int $queryMode - * Used to control the amount of debugging information returned in - * [ResultSetStats][google.spanner.v1.ResultSetStats]. If - * [partition_token][google.spanner.v1.ExecuteSqlRequest.partition_token] is - * set, [query_mode][google.spanner.v1.ExecuteSqlRequest.query_mode] can only - * be set to - * [QueryMode.NORMAL][google.spanner.v1.ExecuteSqlRequest.QueryMode.NORMAL]. - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ExecuteSqlRequest\QueryMode} - * @type string $partitionToken - * If present, results will be restricted to the specified partition - * previously created using PartitionQuery(). There must be an exact - * match for the values of fields common to this message and the - * PartitionQueryRequest message used to create this partition_token. - * @type int $seqno - * A per-transaction sequence number used to identify this request. This field - * makes each request idempotent such that if the request is received multiple - * times, at most one will succeed. - * - * The sequence number must be monotonically increasing within the - * transaction. If a request arrives for the first time with an out-of-order - * sequence number, the transaction may be aborted. Replays of previously - * handled requests will yield the same response as the first execution. - * - * Required for DML statements. Ignored for queries. - * @type QueryOptions $queryOptions - * Query optimizer configuration to use for the given query. - * @type RequestOptions $requestOptions - * Common options for this request. - * @type DirectedReadOptions $directedReadOptions - * Directed read options for this request. - * @type bool $dataBoostEnabled - * If this is for a partitioned query and this field is set to `true`, the - * request is executed with Spanner Data Boost independent compute resources. - * - * If the field is set to `true` but the request does not set - * `partition_token`, the API returns an `INVALID_ARGUMENT` error. - * @type int $timeoutMillis - * Timeout to use for this call. - * } - * - * @return \Google\ApiCore\ServerStream - * - * @throws ApiException if the remote call fails - */ - public function executeStreamingSql( - $session, - $sql, - array $optionalArgs = [] - ) { - $request = new ExecuteSqlRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setSql($sql); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['params'])) { - $request->setParams($optionalArgs['params']); - } - - if (isset($optionalArgs['paramTypes'])) { - $request->setParamTypes($optionalArgs['paramTypes']); - } - - if (isset($optionalArgs['resumeToken'])) { - $request->setResumeToken($optionalArgs['resumeToken']); - } - - if (isset($optionalArgs['queryMode'])) { - $request->setQueryMode($optionalArgs['queryMode']); - } - - if (isset($optionalArgs['partitionToken'])) { - $request->setPartitionToken($optionalArgs['partitionToken']); - } - - if (isset($optionalArgs['seqno'])) { - $request->setSeqno($optionalArgs['seqno']); - } - - if (isset($optionalArgs['queryOptions'])) { - $request->setQueryOptions($optionalArgs['queryOptions']); - } - - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['directedReadOptions'])) { - $request->setDirectedReadOptions( - $optionalArgs['directedReadOptions'] - ); - } - - if (isset($optionalArgs['dataBoostEnabled'])) { - $request->setDataBoostEnabled($optionalArgs['dataBoostEnabled']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'ExecuteStreamingSql', - PartialResultSet::class, - $optionalArgs, - $request, - Call::SERVER_STREAMING_CALL - ); - } - - /** - * Gets a session. Returns `NOT_FOUND` if the session does not exist. - * This is mainly useful for determining whether a session is still - * alive. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedName = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $response = $spannerClient->getSession($formattedName); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $name Required. The name of the session to retrieve. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\Session - * - * @throws ApiException if the remote call fails - */ - public function getSession($name, array $optionalArgs = []) - { - $request = new GetSessionRequest(); - $requestParamHeaders = []; - $request->setName($name); - $requestParamHeaders['name'] = $name; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'GetSession', - Session::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Lists all sessions in a given database. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedDatabase = $spannerClient->databaseName('[PROJECT]', '[INSTANCE]', '[DATABASE]'); - * // Iterate over pages of elements - * $pagedResponse = $spannerClient->listSessions($formattedDatabase); - * foreach ($pagedResponse->iteratePages() as $page) { - * foreach ($page as $element) { - * // doSomethingWith($element); - * } - * } - * // Alternatively: - * // Iterate through all elements - * $pagedResponse = $spannerClient->listSessions($formattedDatabase); - * foreach ($pagedResponse->iterateAllElements() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $database Required. The database in which to list sessions. - * @param array $optionalArgs { - * Optional. - * - * @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 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. - * @type string $filter - * An expression for filtering the results of the request. Filter rules are - * case insensitive. The fields eligible for filtering are: - * - * * `labels.key` where key is the name of a label - * - * Some examples of using filters are: - * - * * `labels.env:*` --> The session has the label "env". - * * `labels.env:dev` --> The session has the label "env" and the value of - * the label contains the string "dev". - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\ApiCore\PagedListResponse - * - * @throws ApiException if the remote call fails - */ - public function listSessions($database, array $optionalArgs = []) - { - $request = new ListSessionsRequest(); - $requestParamHeaders = []; - $request->setDatabase($database); - $requestParamHeaders['database'] = $database; - if (isset($optionalArgs['pageSize'])) { - $request->setPageSize($optionalArgs['pageSize']); - } - - if (isset($optionalArgs['pageToken'])) { - $request->setPageToken($optionalArgs['pageToken']); - } - - if (isset($optionalArgs['filter'])) { - $request->setFilter($optionalArgs['filter']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->getPagedListResponse( - 'ListSessions', - $optionalArgs, - ListSessionsResponse::class, - $request - ); - } - - /** - * Creates a set of partition tokens that can be used to execute a query - * operation in parallel. Each of the returned partition tokens can be used - * by [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] to - * specify a subset of the query result to read. The same session and - * read-only transaction must be used by the PartitionQueryRequest used to - * create the partition tokens and the ExecuteSqlRequests that use the - * partition tokens. - * - * Partition tokens become invalid when the session used to create them - * is deleted, is idle for too long, begins a new transaction, or becomes too - * old. When any of these happen, it is not possible to resume the query, and - * the whole operation must be restarted from the beginning. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $sql = 'sql'; - * $response = $spannerClient->partitionQuery($formattedSession, $sql); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session used to create the partitions. - * @param string $sql Required. The query request to generate partitions for. The request will - * fail if the query is not root partitionable. For a query to be root - * partitionable, it needs to satisfy a few conditions. For example, if the - * query execution plan contains a distributed union operator, then it must be - * the first operator in the plan. For more information about other - * conditions, see [Read data in - * parallel](https://cloud.google.com/spanner/docs/reads#read_data_in_parallel). - * - * The query request must not contain DML commands, such as INSERT, UPDATE, or - * DELETE. Use - * [ExecuteStreamingSql][google.spanner.v1.Spanner.ExecuteStreamingSql] with a - * PartitionedDml transaction for large, partition-friendly DML operations. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * Read only snapshot transactions are supported, read/write and single use - * transactions are not. - * @type Struct $params - * Parameter names and values that bind to placeholders in the SQL string. - * - * A parameter placeholder consists of the `@` character followed by the - * parameter name (for example, `@firstName`). Parameter names can contain - * letters, numbers, and underscores. - * - * Parameters can appear anywhere that a literal value is expected. The same - * parameter name can be used more than once, for example: - * - * `"WHERE id > @msg_id AND id < @msg_id + 100"` - * - * It is an error to execute a SQL statement with unbound parameters. - * @type array $paramTypes - * It is not always possible for Cloud Spanner to infer the right SQL type - * from a JSON value. For example, values of type `BYTES` and values - * of type `STRING` both appear in - * [params][google.spanner.v1.PartitionQueryRequest.params] as JSON strings. - * - * In these cases, `param_types` can be used to specify the exact - * SQL type for some or all of the SQL query parameters. See the - * definition of [Type][google.spanner.v1.Type] for more information - * about SQL types. - * @type PartitionOptions $partitionOptions - * Additional options that affect how many partitions are created. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\PartitionResponse - * - * @throws ApiException if the remote call fails - */ - public function partitionQuery($session, $sql, array $optionalArgs = []) - { - $request = new PartitionQueryRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setSql($sql); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['params'])) { - $request->setParams($optionalArgs['params']); - } - - if (isset($optionalArgs['paramTypes'])) { - $request->setParamTypes($optionalArgs['paramTypes']); - } - - if (isset($optionalArgs['partitionOptions'])) { - $request->setPartitionOptions($optionalArgs['partitionOptions']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'PartitionQuery', - PartitionResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Creates a set of partition tokens that can be used to execute a read - * operation in parallel. Each of the returned partition tokens can be used - * by [StreamingRead][google.spanner.v1.Spanner.StreamingRead] to specify a - * subset of the read result to read. The same session and read-only - * transaction must be used by the PartitionReadRequest used to create the - * partition tokens and the ReadRequests that use the partition tokens. There - * are no ordering guarantees on rows returned among the returned partition - * tokens, or even within each individual StreamingRead call issued with a - * partition_token. - * - * Partition tokens become invalid when the session used to create them - * is deleted, is idle for too long, begins a new transaction, or becomes too - * old. When any of these happen, it is not possible to resume the read, and - * the whole operation must be restarted from the beginning. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $table = 'table'; - * $keySet = new KeySet(); - * $response = $spannerClient->partitionRead($formattedSession, $table, $keySet); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session used to create the partitions. - * @param string $table Required. The name of the table in the database to be read. - * @param KeySet $keySet Required. `key_set` identifies the rows to be yielded. `key_set` names the - * primary keys of the rows in - * [table][google.spanner.v1.PartitionReadRequest.table] to be yielded, unless - * [index][google.spanner.v1.PartitionReadRequest.index] is present. If - * [index][google.spanner.v1.PartitionReadRequest.index] is present, then - * [key_set][google.spanner.v1.PartitionReadRequest.key_set] instead names - * index keys in [index][google.spanner.v1.PartitionReadRequest.index]. - * - * It is not an error for the `key_set` to name rows that do not - * exist in the database. Read yields nothing for nonexistent rows. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * Read only snapshot transactions are supported, read/write and single use - * transactions are not. - * @type string $index - * If non-empty, the name of an index on - * [table][google.spanner.v1.PartitionReadRequest.table]. This index is used - * instead of the table primary key when interpreting - * [key_set][google.spanner.v1.PartitionReadRequest.key_set] and sorting - * result rows. See [key_set][google.spanner.v1.PartitionReadRequest.key_set] - * for further information. - * @type string[] $columns - * The columns of [table][google.spanner.v1.PartitionReadRequest.table] to be - * returned for each row matching this request. - * @type PartitionOptions $partitionOptions - * Additional options that affect how many partitions are created. - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\PartitionResponse - * - * @throws ApiException if the remote call fails - */ - public function partitionRead( - $session, - $table, - $keySet, - array $optionalArgs = [] - ) { - $request = new PartitionReadRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setTable($table); - $request->setKeySet($keySet); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['index'])) { - $request->setIndex($optionalArgs['index']); - } - - if (isset($optionalArgs['columns'])) { - $request->setColumns($optionalArgs['columns']); - } - - if (isset($optionalArgs['partitionOptions'])) { - $request->setPartitionOptions($optionalArgs['partitionOptions']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'PartitionRead', - PartitionResponse::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Reads rows from the database using key lookups and scans, as a - * simple key/value style alternative to - * [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. This method cannot be - * used to return a result set larger than 10 MiB; if the read matches more - * data than that, the read fails with a `FAILED_PRECONDITION` - * error. - * - * Reads inside read-write transactions might return `ABORTED`. If - * this occurs, the application should restart the transaction from - * the beginning. See [Transaction][google.spanner.v1.Transaction] for more - * details. - * - * Larger result sets can be yielded in streaming fashion by calling - * [StreamingRead][google.spanner.v1.Spanner.StreamingRead] instead. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $table = 'table'; - * $columns = []; - * $keySet = new KeySet(); - * $response = $spannerClient->read($formattedSession, $table, $columns, $keySet); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the read should be performed. - * @param string $table Required. The name of the table in the database to be read. - * @param string[] $columns Required. The columns of [table][google.spanner.v1.ReadRequest.table] to be - * returned for each row matching this request. - * @param KeySet $keySet Required. `key_set` identifies the rows to be yielded. `key_set` names the - * primary keys of the rows in [table][google.spanner.v1.ReadRequest.table] to - * be yielded, unless [index][google.spanner.v1.ReadRequest.index] is present. - * If [index][google.spanner.v1.ReadRequest.index] is present, then - * [key_set][google.spanner.v1.ReadRequest.key_set] instead names index keys - * in [index][google.spanner.v1.ReadRequest.index]. - * - * If the [partition_token][google.spanner.v1.ReadRequest.partition_token] - * field is empty, rows are yielded in table primary key order (if - * [index][google.spanner.v1.ReadRequest.index] is empty) or index key order - * (if [index][google.spanner.v1.ReadRequest.index] is non-empty). If the - * [partition_token][google.spanner.v1.ReadRequest.partition_token] field is - * not empty, rows will be yielded in an unspecified order. - * - * It is not an error for the `key_set` to name rows that do not - * exist in the database. Read yields nothing for nonexistent rows. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * The transaction to use. If none is provided, the default is a - * temporary read-only transaction with strong concurrency. - * @type string $index - * If non-empty, the name of an index on - * [table][google.spanner.v1.ReadRequest.table]. This index is used instead of - * the table primary key when interpreting - * [key_set][google.spanner.v1.ReadRequest.key_set] and sorting result rows. - * See [key_set][google.spanner.v1.ReadRequest.key_set] for further - * information. - * @type int $limit - * If greater than zero, only the first `limit` rows are yielded. If `limit` - * is zero, the default is no limit. A limit cannot be specified if - * `partition_token` is set. - * @type string $resumeToken - * If this request is resuming a previously interrupted read, - * `resume_token` should be copied from the last - * [PartialResultSet][google.spanner.v1.PartialResultSet] yielded before the - * interruption. Doing this enables the new read to resume where the last read - * left off. The rest of the request parameters must exactly match the request - * that yielded this token. - * @type string $partitionToken - * If present, results will be restricted to the specified partition - * previously created using PartitionRead(). There must be an exact - * match for the values of fields common to this message and the - * PartitionReadRequest message used to create this partition_token. - * @type RequestOptions $requestOptions - * Common options for this request. - * @type DirectedReadOptions $directedReadOptions - * Directed read options for this request. - * @type bool $dataBoostEnabled - * If this is for a partitioned read and this field is set to `true`, the - * request is executed with Spanner Data Boost independent compute resources. - * - * If the field is set to `true` but the request does not set - * `partition_token`, the API returns an `INVALID_ARGUMENT` error. - * @type int $orderBy - * Optional. Order for the returned rows. - * - * By default, Spanner will return result rows in primary key order except for - * PartitionRead requests. For applications that do not require rows to be - * returned in primary key (`ORDER_BY_PRIMARY_KEY`) order, setting - * `ORDER_BY_NO_ORDER` option allows Spanner to optimize row retrieval, - * resulting in lower latencies in certain cases (e.g. bulk point lookups). - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ReadRequest\OrderBy} - * @type int $lockHint - * Optional. Lock Hint for the request, it can only be used with read-write - * transactions. - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ReadRequest\LockHint} - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @return \Google\Cloud\Spanner\V1\ResultSet - * - * @throws ApiException if the remote call fails - */ - public function read( - $session, - $table, - $columns, - $keySet, - array $optionalArgs = [] - ) { - $request = new ReadRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setTable($table); - $request->setColumns($columns); - $request->setKeySet($keySet); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['index'])) { - $request->setIndex($optionalArgs['index']); - } - - if (isset($optionalArgs['limit'])) { - $request->setLimit($optionalArgs['limit']); - } - - if (isset($optionalArgs['resumeToken'])) { - $request->setResumeToken($optionalArgs['resumeToken']); - } - - if (isset($optionalArgs['partitionToken'])) { - $request->setPartitionToken($optionalArgs['partitionToken']); - } - - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['directedReadOptions'])) { - $request->setDirectedReadOptions( - $optionalArgs['directedReadOptions'] - ); - } - - if (isset($optionalArgs['dataBoostEnabled'])) { - $request->setDataBoostEnabled($optionalArgs['dataBoostEnabled']); - } - - if (isset($optionalArgs['orderBy'])) { - $request->setOrderBy($optionalArgs['orderBy']); - } - - if (isset($optionalArgs['lockHint'])) { - $request->setLockHint($optionalArgs['lockHint']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'Read', - ResultSet::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Rolls back a transaction, releasing any locks it holds. It is a good - * idea to call this for any transaction that includes one or more - * [Read][google.spanner.v1.Spanner.Read] or - * [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql] requests and ultimately - * decides not to commit. - * - * `Rollback` returns `OK` if it successfully aborts the transaction, the - * transaction was already aborted, or the transaction is not - * found. `Rollback` never returns `ABORTED`. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $transactionId = '...'; - * $spannerClient->rollback($formattedSession, $transactionId); - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the transaction to roll back is running. - * @param string $transactionId Required. The transaction to roll back. - * @param array $optionalArgs { - * Optional. - * - * @type RetrySettings|array $retrySettings - * Retry settings to use for this call. Can be a {@see RetrySettings} object, or an - * associative array of retry settings parameters. See the documentation on - * {@see RetrySettings} for example usage. - * } - * - * @throws ApiException if the remote call fails - */ - public function rollback($session, $transactionId, array $optionalArgs = []) - { - $request = new RollbackRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setTransactionId($transactionId); - $requestParamHeaders['session'] = $session; - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'Rollback', - GPBEmpty::class, - $optionalArgs, - $request - )->wait(); - } - - /** - * Like [Read][google.spanner.v1.Spanner.Read], except returns the result set - * as a stream. Unlike [Read][google.spanner.v1.Spanner.Read], there is no - * limit on the size of the returned result set. However, no individual row in - * the result set can exceed 100 MiB, and no column value can exceed - * 10 MiB. - * - * Sample code: - * ``` - * $spannerClient = new SpannerClient(); - * try { - * $formattedSession = $spannerClient->sessionName('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]'); - * $table = 'table'; - * $columns = []; - * $keySet = new KeySet(); - * // Read all responses until the stream is complete - * $stream = $spannerClient->streamingRead($formattedSession, $table, $columns, $keySet); - * foreach ($stream->readAll() as $element) { - * // doSomethingWith($element); - * } - * } finally { - * $spannerClient->close(); - * } - * ``` - * - * @param string $session Required. The session in which the read should be performed. - * @param string $table Required. The name of the table in the database to be read. - * @param string[] $columns Required. The columns of [table][google.spanner.v1.ReadRequest.table] to be - * returned for each row matching this request. - * @param KeySet $keySet Required. `key_set` identifies the rows to be yielded. `key_set` names the - * primary keys of the rows in [table][google.spanner.v1.ReadRequest.table] to - * be yielded, unless [index][google.spanner.v1.ReadRequest.index] is present. - * If [index][google.spanner.v1.ReadRequest.index] is present, then - * [key_set][google.spanner.v1.ReadRequest.key_set] instead names index keys - * in [index][google.spanner.v1.ReadRequest.index]. - * - * If the [partition_token][google.spanner.v1.ReadRequest.partition_token] - * field is empty, rows are yielded in table primary key order (if - * [index][google.spanner.v1.ReadRequest.index] is empty) or index key order - * (if [index][google.spanner.v1.ReadRequest.index] is non-empty). If the - * [partition_token][google.spanner.v1.ReadRequest.partition_token] field is - * not empty, rows will be yielded in an unspecified order. - * - * It is not an error for the `key_set` to name rows that do not - * exist in the database. Read yields nothing for nonexistent rows. - * @param array $optionalArgs { - * Optional. - * - * @type TransactionSelector $transaction - * The transaction to use. If none is provided, the default is a - * temporary read-only transaction with strong concurrency. - * @type string $index - * If non-empty, the name of an index on - * [table][google.spanner.v1.ReadRequest.table]. This index is used instead of - * the table primary key when interpreting - * [key_set][google.spanner.v1.ReadRequest.key_set] and sorting result rows. - * See [key_set][google.spanner.v1.ReadRequest.key_set] for further - * information. - * @type int $limit - * If greater than zero, only the first `limit` rows are yielded. If `limit` - * is zero, the default is no limit. A limit cannot be specified if - * `partition_token` is set. - * @type string $resumeToken - * If this request is resuming a previously interrupted read, - * `resume_token` should be copied from the last - * [PartialResultSet][google.spanner.v1.PartialResultSet] yielded before the - * interruption. Doing this enables the new read to resume where the last read - * left off. The rest of the request parameters must exactly match the request - * that yielded this token. - * @type string $partitionToken - * If present, results will be restricted to the specified partition - * previously created using PartitionRead(). There must be an exact - * match for the values of fields common to this message and the - * PartitionReadRequest message used to create this partition_token. - * @type RequestOptions $requestOptions - * Common options for this request. - * @type DirectedReadOptions $directedReadOptions - * Directed read options for this request. - * @type bool $dataBoostEnabled - * If this is for a partitioned read and this field is set to `true`, the - * request is executed with Spanner Data Boost independent compute resources. - * - * If the field is set to `true` but the request does not set - * `partition_token`, the API returns an `INVALID_ARGUMENT` error. - * @type int $orderBy - * Optional. Order for the returned rows. - * - * By default, Spanner will return result rows in primary key order except for - * PartitionRead requests. For applications that do not require rows to be - * returned in primary key (`ORDER_BY_PRIMARY_KEY`) order, setting - * `ORDER_BY_NO_ORDER` option allows Spanner to optimize row retrieval, - * resulting in lower latencies in certain cases (e.g. bulk point lookups). - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ReadRequest\OrderBy} - * @type int $lockHint - * Optional. Lock Hint for the request, it can only be used with read-write - * transactions. - * For allowed values, use constants defined on {@see \Google\Cloud\Spanner\V1\ReadRequest\LockHint} - * @type int $timeoutMillis - * Timeout to use for this call. - * } - * - * @return \Google\ApiCore\ServerStream - * - * @throws ApiException if the remote call fails - */ - public function streamingRead( - $session, - $table, - $columns, - $keySet, - array $optionalArgs = [] - ) { - $request = new ReadRequest(); - $requestParamHeaders = []; - $request->setSession($session); - $request->setTable($table); - $request->setColumns($columns); - $request->setKeySet($keySet); - $requestParamHeaders['session'] = $session; - if (isset($optionalArgs['transaction'])) { - $request->setTransaction($optionalArgs['transaction']); - } - - if (isset($optionalArgs['index'])) { - $request->setIndex($optionalArgs['index']); - } - - if (isset($optionalArgs['limit'])) { - $request->setLimit($optionalArgs['limit']); - } - - if (isset($optionalArgs['resumeToken'])) { - $request->setResumeToken($optionalArgs['resumeToken']); - } - - if (isset($optionalArgs['partitionToken'])) { - $request->setPartitionToken($optionalArgs['partitionToken']); - } - - if (isset($optionalArgs['requestOptions'])) { - $request->setRequestOptions($optionalArgs['requestOptions']); - } - - if (isset($optionalArgs['directedReadOptions'])) { - $request->setDirectedReadOptions( - $optionalArgs['directedReadOptions'] - ); - } - - if (isset($optionalArgs['dataBoostEnabled'])) { - $request->setDataBoostEnabled($optionalArgs['dataBoostEnabled']); - } - - if (isset($optionalArgs['orderBy'])) { - $request->setOrderBy($optionalArgs['orderBy']); - } - - if (isset($optionalArgs['lockHint'])) { - $request->setLockHint($optionalArgs['lockHint']); - } - - $requestParams = new RequestParamsHeaderDescriptor( - $requestParamHeaders - ); - $optionalArgs['headers'] = isset($optionalArgs['headers']) - ? array_merge($requestParams->getHeader(), $optionalArgs['headers']) - : $requestParams->getHeader(); - return $this->startCall( - 'StreamingRead', - PartialResultSet::class, - $optionalArgs, - $request, - Call::SERVER_STREAMING_CALL - ); - } -} 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/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..9b13bc49ebe8 100644 --- a/Spanner/tests/ResultGeneratorTrait.php +++ b/Spanner/tests/ResultGeneratorTrait.php @@ -17,91 +17,128 @@ 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([ + 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' ] - ], $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. - * **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 yieldRows(array $rows, $withStats = false, $transaction = null) - { - $fields = []; - $values = []; - foreach ($rows as $row) { - $fields[] = [ - 'name' => $row['name'], - 'type' => [ - 'code' => $row['type'] - ] - ]; + $rows = []; - $values[] = $row['value']; + if ($chunks) { + foreach ($chunks as $i => $chunk) { + if (is_string($chunk)) { + // merge from JSON string + $result = new PartialResultSet(); + $result->mergeFromJsonString($chunk); + $rows[$i] = $result; + } elseif ($chunk instanceof PartialResultSet) { + $rows[$i] = $chunk; + } + } } - $result = [ - 'metadata' => [ - 'rowType' => [ - 'fields' => $fields - ] - ], - 'values' => $values - ]; + 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']]); + } - if ($withStats) { - $result['stats'] = [ - 'rowCountExact' => 1, - 'rowCountLowerBound' => 1 + $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; + } + + $rows[] = new PartialResultSet($result); } - if ($transaction) { - $result['metadata']['transaction'] = [ - 'id' => $transaction - ]; + $this->stream->readAll() + ->willReturn($this->resultGeneratorArray($rows)); + + return $this->stream->reveal(); + } + + private function resultGeneratorArray($chunks) + { + foreach ($chunks as $chunk) { + yield $chunk; } + } - yield $result; + private function resultGeneratorJson($chunks) + { + foreach ($chunks as $chunk) { + yield json_decode($chunk, true); + } } - /** - * Yield the given array as a generator. - * - * @param array $data The input data - * @return \Generator - */ - private function resultGeneratorData(array $data) + private function getStreamingDataFixture() { - yield $data; + 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..52fbae8d5b93 100644 --- a/Spanner/tests/Snippet/BackupTest.php +++ b/Spanner/tests/Snippet/BackupTest.php @@ -17,21 +17,35 @@ 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 Google\Protobuf\Timestamp as TimestampProto; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; - /** +/** * @group spanner * @group spanner-backup */ @@ -45,9 +59,10 @@ class BackupTest extends SnippetTestCase const DATABASE = 'my-database'; const BACKUP = 'my-backup'; - private $connection; + private $serializer; + private $operationResponse; + private $databaseAdminClient; private $backup; - private $client; private $instance; private $expireTime; @@ -55,25 +70,28 @@ 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->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)); + $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,33 +111,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->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } 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); + $snippet = $this->snippetFromMethod(Backup::class, 'createCopy'); - $snippet->addLocal('spanner', $this->client); + $snippet->addLocal('spanner', $spanner->reveal()); - $this->connection->copyBackup(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => 'my-operation' - ]); + $this->databaseAdminClient->copyBackup( + Argument::type(CopyBackupRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->operationResponse->reveal()); - $this->client->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testDelete() @@ -127,10 +157,12 @@ 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 +172,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 +190,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 +217,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 +240,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 +254,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 TimestampProto(['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', new SpannerClient(['projectId' => 'my-project'])); $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 +295,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..c08cf9096dd0 100644 --- a/Spanner/tests/Snippet/Batch/BatchClientTest.php +++ b/Spanner/tests/Snippet/Batch/BatchClientTest.php @@ -17,22 +17,34 @@ 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\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; -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 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; /** @@ -41,26 +53,28 @@ */ class BatchClientTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; - use OperationRefreshTrait; - use StubCreationTrait; + 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'; 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->spannerClient = $this->prophesize(SpannerClient::class); + $this->serializer = new Serializer(); + $this->client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ], ['operation']); + ); } public function testClass() @@ -100,105 +114,112 @@ 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->___setProperty('requestHandler', $requestHandler->reveal()); + $pubsub->subscription(Argument::cetera()) + ->shouldBeCalledOnce() + ->willReturn($subscription->reveal()); // setup spanner service call stubs - $this->connection->partitionQuery(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->partitionQuery( + 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->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( + Argument::that(function ($request) use ($partition1) { + $message = $this->serializer->encodeMessage($request); + $this->assertEquals( + $message['partitionToken'], + $partition1->token() + ); + $this->assertEquals( + $message['transaction']['id'], + self::TRANSACTION + ); + $this->assertEquals($message['session'], self::SESSION); + return true; + }), + 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->connection->createSession(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'name' => self::SESSION - ]); - - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + )])); + + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + 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->connection->deleteSession(Argument::any()) - ->shouldBeCalled(); + $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->connection->reveal()); $publisher->invoke(); - $subscriber->invoke(); } @@ -209,19 +230,18 @@ 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( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $time)->format(Timestamp::FORMAT) - ]); - - $this->refreshOperation($this->client, $this->connection->reveal()); + 'read_timestamp' => new TimestampProto(['seconds' => $time]) + ])); + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); $res = $snippet->invoke('snapshot'); $this->assertInstanceOf(BatchSnapshot::class, $res->returnVal()); @@ -229,7 +249,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..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; @@ -27,13 +28,23 @@ 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 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 @@ -42,15 +53,15 @@ class BatchSnapshotTest extends SnippetTestCase { use GrpcTestTrait; - use OperationRefreshTrait; use ProphecyTrait; - use StubCreationTrait; + 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'; const TRANSACTION = 'transaction-id'; - private $connection; + private $spannerClient; + private $serializer; private $session; private $time; private $snapshot; @@ -59,7 +70,8 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); - $this->connection = $this->getConnStub(); + $this->serializer = new Serializer(); + $this->spannerClient = $this->prophesize(SpannerClient::class); $sessData = SpannerClient::parseName(self::SESSION, 'session'); $this->session = $this->prophesize(Session::class); @@ -70,34 +82,38 @@ public function setUp(): void ]); $this->time = time(); - $this->snapshot = TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->connection->reveal(), 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->connection->createSession(Argument::any()) - ->shouldBeCalledTimes(1) - ->willReturn([ - 'name' => self::SESSION - ]); - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + 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->connection->reveal(), false), + $client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ]); + ); $snippet = $this->snippetFromClass(BatchSnapshot::class); $snippet->setLine(3, ''); @@ -121,7 +137,7 @@ public function testSerializeSnapshot($index) public function provideSerializeIndex() { - return [[1],[2]]; + return [[1], [2]]; } public function testClose() @@ -129,7 +145,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); @@ -137,31 +152,44 @@ public function testClose() $res = $snippet->invoke(); } - /** - * @dataProvider providePartitionMethods - */ - public function testPartitionRead($method) + public function testPartitionRead() { - $this->connection->$method(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $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->connection->reveal()); + ])); - $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() @@ -171,25 +199,31 @@ public function testExecutePartition() $opts = []; $partition = new QueryPartition($token, $sql, $opts); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'loginCount', - 'type' => [ - 'code' => Database::TYPE_INT64 + $this->spannerClient->executeStreamingSql( + 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->connection->reveal()); + ] + )])); $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..bb6db70a170c 100644 --- a/Spanner/tests/Snippet/Batch/QueryPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/QueryPartitionTest.php @@ -22,9 +22,19 @@ 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\Tests\StubCreationTrait; 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; /** @@ -33,14 +43,16 @@ */ class QueryPartitionTest extends SnippetTestCase { + use ProphecyTrait; 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,36 +61,41 @@ 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); } 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( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new Transaction([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ]); - $connection->partitionQuery(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) + ]) + ); + $this->spannerClient->partitionQuery( + 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($connection->reveal(), 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 c32e3af55516..df076c4e51c7 100644 --- a/Spanner/tests/Snippet/Batch/ReadPartitionTest.php +++ b/Spanner/tests/Snippet/Batch/ReadPartitionTest.php @@ -22,10 +22,20 @@ 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\Tests\StubCreationTrait; 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; /** @@ -34,16 +44,18 @@ */ class ReadPartitionTest extends SnippetTestCase { + use ProphecyTrait; use GrpcTestTrait; 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 +66,8 @@ public function setUp(): void { $this->checkAndSkipGrpcTests(); + $this->spannerClient = $this->prophesize(SpannerClient::class); + $this->serializer = new Serializer(); $this->time = time(); $this->table = 'table'; $this->keySet = new KeySet(['all' => true]); @@ -63,30 +77,34 @@ 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( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + )->willReturn(new SessionProto(['name' => self::SESSION])); + + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new Transaction([ 'id' => self::TRANSACTION, - 'readTimestamp' => \DateTime::createFromFormat('U', (string) $this->time)->format(Timestamp::FORMAT) - ]); - $connection->partitionRead(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + 'read_timestamp' => new TimestampProto(['seconds' => $this->time]) + ]) + ); + $this->spannerClient->partitionRead( + 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($connection->reveal(), false), + $client = new BatchClient( + new Operation($this->spannerClient->reveal(), $this->serializer, false), self::DATABASE - ]); + ); $snippet = $this->snippetFromClass(ReadPartition::class); $snippet->setLine(4, ''); diff --git a/Spanner/tests/Snippet/BatchDmlResultTest.php b/Spanner/tests/Snippet/BatchDmlResultTest.php index f6945b8ca479..24df03f8e9f9 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; @@ -25,9 +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\Tests\StubCreationTrait; +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; @@ -38,15 +46,18 @@ 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->spannerClient = $this->prophesize(SpannerClient::class); $this->result = new BatchDmlResult([ 'resultSets' => [ [ @@ -69,24 +80,23 @@ 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( + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse(['result_sets' => []])); + + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => 'id'])); + + $this->spannerClient->commit( + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $session = $this->prophesize(Session::class); $session->name()->willReturn( @@ -108,15 +118,17 @@ public function testClass() $instance->name()->willReturn('projects/test-project/instances/my-instance'); $instance->directedReadOptions()->willReturn([]); - $database = TestHelpers::stub(Database::class, [ - $connection->reveal(), + $databaseAdminClient = $this->prophesize(DatabaseAdminClient::class); + + $database = new Database( + $this->spannerClient->reveal(), + $databaseAdminClient->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->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/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..9fc76a206055 100644 --- a/Spanner/tests/Snippet/CommitTimestampTest.php +++ b/Spanner/tests/Snippet/CommitTimestampTest.php @@ -19,11 +19,18 @@ 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 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; /** @@ -32,13 +39,18 @@ */ class CommitTimestampTest extends SnippetTestCase { + use ProphecyTrait; 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->spannerClient = $this->prophesize(GapicSpannerClient::class); $this->checkAndSkipGrpcTests(); } @@ -46,31 +58,49 @@ public function testClass() { $id = 'abc'; - $client = TestHelpers::stub(SpannerClient::class); - $conn = $this->getConnStub(); - $conn->createSession(Argument::any()) - ->willReturn([ - 'name' => self::SESSION - ]); + $this->spannerClient->createSession( + Argument::type(CreateSessionRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->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', 'columns' => ['id', 'commitTimestamp'], - 'values' => [$id, CommitTimestamp::SPECIAL_VALUE] + 'values' => [[$id, CommitTimestamp::SPECIAL_VALUE]] ] ]; + $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(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); - $conn->commit(Argument::withEntry('mutations', [$mutation]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => \DateTime::createFromFormat('U', (string) time())->format(Timestamp::FORMAT) + $client = new SpannerClient([ + 'projectId' => 'my-project', + 'gapicSpannerClient' => $this->spannerClient->reveal() ]); - $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..cbecc6dab3d0 100644 --- a/Spanner/tests/Snippet/DatabaseTest.php +++ b/Spanner/tests/Snippet/DatabaseTest.php @@ -17,29 +17,55 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\Iam\Iam; +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\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\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; -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\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; @@ -50,10 +76,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 +85,11 @@ class DatabaseTest extends SnippetTestCase const TRANSACTION = 'my-transaction'; const BACKUP = 'my-backup'; - private $connection; + private $spannerClient; + private $databaseAdminClient; + private $instanceAdminClient; + private $operationResponse; + private $serializer; private $database; private $instance; @@ -69,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([ @@ -86,24 +122,24 @@ 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->instance = new Instance( + $this->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, self::PROJECT, self::INSTANCE - ], ['connection', 'lroConnection']); + ); - $this->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->database = new Database( + $this->spannerClient->reveal(), + $this->databaseAdminClient->reveal(), + $this->serializer, $this->instance, - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['connection', 'operation', 'lroConnection']); + ); } public function testClass() @@ -135,11 +171,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 DatabaseProto(['state' => Database::STATE_READY])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke(); $this->assertEquals('Database is ready!', $res->output()); @@ -153,17 +191,25 @@ public function testBackups() $snippet = $this->snippetFromMethod(Database::class, 'backups'); $snippet->addLocal('database', $this->database); - $this->connection->listBackups(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ - 'backups' => [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) - ] - ] - ]); + $backup = new BackupProto([ + 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) + ]); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $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($pagedListResponse->reveal()); $res = $snippet->invoke('backups'); @@ -179,13 +225,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->operationResponse->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 +252,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 DatabaseProto(['name' => 'foo'])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke(); $this->assertEquals('Database exists!', $res->output()); @@ -219,19 +269,19 @@ public function testExists() */ public function testInfo() { - $db = ['name' => 'foo']; - $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 DatabaseProto(['name' => 'foo'])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); - $this->assertEquals($db, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); } @@ -240,19 +290,19 @@ public function testInfo() */ public function testReload() { - $db = ['name' => 'foo']; - $snippet = $this->snippetFromMethod(Database::class, 'reload'); $snippet->addLocal('database', $this->database); - $this->connection->getDatabase(Argument::any()) + $this->databaseAdminClient->getDatabase( + Argument::type(GetDatabaseRequest::class), + Argument::type('array') + ) ->shouldBeCalledTimes(2) - ->willReturn($db); + ->willReturn(new DatabaseProto(['name' => 'foo'])); - $this->database->___setProperty('connection', $this->connection->reveal()); $res = $snippet->invoke('info'); - $this->assertEquals($db, $res->returnVal()); + $this->assertEquals('foo', $res->returnVal()['name']); $snippet->invoke(); } @@ -264,16 +314,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->operationResponse->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 +336,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->operationResponse->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 +356,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->operationResponse->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -325,13 +375,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->operationResponse->reveal()); - $this->database->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -344,10 +394,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 +417,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 +431,11 @@ public function testDdl() public function testSnapshot() { - $this->connection->beginTransaction(Argument::any(), Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $snippet = $this->snippetFromMethod(Database::class, 'snapshot'); $snippet->addLocal('database', $this->database); @@ -396,14 +446,14 @@ public function testSnapshot() public function testSnapshotReadTimestamp() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto([ 'id' => self::TRANSACTION, - 'readTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + 'read_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'snapshot', 1); $snippet->addLocal('database', $this->database); @@ -414,24 +464,25 @@ public function testSnapshotReadTimestamp() public function testRunTransaction() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); - - $this->connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $this->connection->rollback(Argument::any()) - ->shouldNotBeCalled(); + $this->spannerClient->commit( + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->yieldRows([ + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([ [ 'name' => 'loginCount', 'type' => Database::TYPE_INT64, @@ -439,7 +490,7 @@ public function testRunTransaction() ] ])); - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->rollback(Argument::cetera())->shouldNotBeCalled(); $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); @@ -452,38 +503,47 @@ 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->connection->executeStreamingSql( - Argument::withEntry('transaction', ['begin' => ['readWrite' => []]]) + $this->spannerClient->rollback( + Argument::type(RollbackRequest::class), + Argument::type('array') ) - ->shouldBeCalled() - ->willReturn($this->resultGeneratorData([ - 'metadata' => [ - 'rowType' => [ - 'fields' => [ - [ - 'name' => 'timestamp', - 'type' => [ - 'code' => Database::TYPE_TIMESTAMP + ->shouldBeCalledOnce(); + + $this->spannerClient->executeStreamingSql( + Argument::that(function ($request) { + $this->assertEquals( + $this->serializer->encodeMessage($request)['transaction']['begin']['readWrite'], + ['readLockMode' => 0, 'multiplexedSessionPreviousTransactionId' => ''] + ); + return true; + }), + 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->connection->reveal()); + )])); $snippet = $this->snippetFromMethod(Database::class, 'runTransaction'); $snippet->addUse(Transaction::class); @@ -496,13 +556,11 @@ public function testRunTransactionRollback() public function testTransaction() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); $snippet = $this->snippetFromMethod(Database::class, 'transaction'); $snippet->addLocal('database', $this->database); @@ -512,37 +570,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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['insert']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['insert']) + && isset($message['mutations'][1]['insert']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insertBatch'); $snippet->addLocal('database', $this->database); @@ -551,37 +609,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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['update']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $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; - } - - return true; - }))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['update']) + && isset($message['mutations'][1]['update']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'updateBatch'); $snippet->addLocal('database', $this->database); @@ -590,37 +648,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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['insertOrUpdate']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $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; - } - - 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->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['insertOrUpdate']) + && isset($message['mutations'][1]['insertOrUpdate']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'insertOrUpdateBatch'); $snippet->addLocal('database', $this->database); @@ -629,37 +687,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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['replace']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['replace']) + && isset($message['mutations'][1]['replace']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'replaceBatch'); $snippet->addLocal('database', $this->database); @@ -668,13 +726,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->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::that(function ($request) { + $message = $this->serializer->encodeMessage($request); + return isset($message['mutations'][0]['delete']); + }), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ])); $snippet = $this->snippetFromMethod(Database::class, 'delete'); $snippet->addUse(KeySet::class); @@ -684,11 +746,12 @@ public function testDelete() public function testExecute() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream()); $snippet = $this->snippetFromMethod(Database::class, 'execute'); $snippet->addLocal('database', $this->database); @@ -699,37 +762,57 @@ 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 + $message = $this->serializer->decodeMessage( + new PartialResultSet(), + [ + 'metadata' => [ + 'rowType' => [ + 'fields' => [ + [ + 'name' => 'timestamp', + 'type' => [ + 'code' => Database::TYPE_TIMESTAMP + ] ] ] ] + ], + 'values' => [ + ['nullValue' => 0] ] - ], - 'values' => [null] - ])); - - $this->refreshOperation($this->database, $this->connection->reveal()); + ] + ); + + $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); @@ -740,44 +823,39 @@ 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( + 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; + }), + 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' => [[]] ] - ], - 'values' => [[]] - ])); - - $this->refreshOperation($this->database, $this->connection->reveal()); + )])); $snippet = $this->snippetFromMethod(Database::class, 'execute', 2); $snippet->addLocal('database', $this->database); @@ -788,11 +866,13 @@ public function testExecuteWithEmptyArray() public function testExecuteBeginSnapshot() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) + ); $snippet = $this->snippetFromMethod(Database::class, 'execute', 5); $snippet->addLocal('database', $this->database); @@ -804,11 +884,13 @@ public function testExecuteBeginSnapshot() public function testExecuteBeginTransaction() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) + ); $snippet = $this->snippetFromMethod(Database::class, 'execute', 6); $snippet->addLocal('database', $this->database); @@ -820,17 +902,19 @@ public function testExecuteBeginTransaction() public function testExecutePartitionedUpdate() { - $this->connection->beginTransaction(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'id' => self::TRANSACTION - ]); - - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(true)); + $this->spannerClient->beginTransaction( + Argument::type(BeginTransactionRequest::class), + Argument::type('array') + ) + ->willReturn(new TransactionProto(['id' => self::TRANSACTION])); - $this->refreshOperation($this->database, $this->connection->reveal()); + $stats = new ResultSetStats(['row_count_lower_bound' => 1]); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream([], $stats)); $snippet = $this->snippetFromMethod(Database::class, 'executePartitionedUpdate'); $snippet->addLocal('database', $this->database); @@ -841,11 +925,10 @@ public function testExecutePartitionedUpdate() public function testRead() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream()); $snippet = $this->snippetFromMethod(Database::class, 'read'); $snippet->addLocal('database', $this->database); @@ -856,11 +939,10 @@ public function testRead() public function testReadWithSnapshot() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->streamingRead( + 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); @@ -872,11 +954,11 @@ public function testReadWithSnapshot() public function testReadWithTransaction() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(false, self::TRANSACTION)); - - $this->refreshOperation($this->database, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream([], null, self::TRANSACTION) + ); $snippet = $this->snippetFromMethod(Database::class, 'read', 2); $snippet->addLocal('database', $this->database); @@ -901,6 +983,7 @@ public function testClose() $snippet->addLocal('database', $this->database); $res = $snippet->invoke(); + $this->assertNull($res->returnVal()); } public function testIam() @@ -909,18 +992,18 @@ 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() { - $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(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() @@ -928,21 +1011,27 @@ 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' - ] - ] - ]); + $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->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..b1aad898eb5e 100644 --- a/Spanner/tests/Snippet/DateTest.php +++ b/Spanner/tests/Snippet/DateTest.php @@ -17,10 +17,11 @@ namespace Google\Cloud\Spanner\Tests\Snippet; +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; -use Google\Cloud\Core\Testing\GrpcTestTrait; /** * @group spanner @@ -36,7 +37,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..291900390d96 100644 --- a/Spanner/tests/Snippet/InstanceConfigurationTest.php +++ b/Spanner/tests/Snippet/InstanceConfigurationTest.php @@ -17,16 +17,20 @@ 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\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 Google\Cloud\Spanner\Tests\StubCreationTrait; -use Prophecy\Argument; +use Google\Cloud\Spanner\Serializer; use Prophecy\PhpUnit\ProphecyTrait; +use Prophecy\Argument; /** * @group spanner @@ -36,31 +40,39 @@ class InstanceConfigurationTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const CONFIG = 'regional-europe-west'; - private $connection; + private $instanceAdminClient; + private $operationResponse; + 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->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(), + $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 +85,26 @@ 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($this->operationResponse->reveal()); + $baseConfig = new InstanceConfiguration( - $dummyConnection, + $this->instanceAdminClient->reveal(), + $this->serializer, self::PROJECT, self::CONFIG, - [], - $this->prophesize(LongRunningConnectionInterface::class)->reveal() + [] ); - $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 +112,13 @@ 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('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -113,10 +127,12 @@ public function testDelete() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'delete'); $snippet->addLocal('instanceConfig', $this->config); - $this->connection->deleteInstanceConfig(Argument::any()) - ->shouldBeCalled(); + $this->instanceAdminClient->deleteInstanceConfig( + Argument::type(DeleteInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->config->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -139,14 +155,19 @@ public function testInfo() 'displayName' => self::CONFIG ]; - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn($info); - - $this->config->___setProperty('connection', $this->connection->reveal()); + $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() @@ -154,14 +175,15 @@ public function testExists() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'exists'); $snippet->addLocal('configuration', $this->config); - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->instanceAdminClient->getInstanceConfig( + Argument::type(GetInstanceConfigRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn(new InstanceConfig([ 'name' => InstanceAdminClient::instanceConfigName(self::PROJECT, self::CONFIG), - 'displayName' => self::CONFIG - ]); - - $this->config->___setProperty('connection', $this->connection->reveal()); + 'display_name' => self::CONFIG + ])); $res = $snippet->invoke(); $this->assertEquals('Configuration exists!', $res->output()); @@ -177,13 +199,18 @@ public function testReload() $snippet = $this->snippetFromMethod(InstanceConfiguration::class, 'reload'); $snippet->addLocal('configuration', $this->config); - $this->connection->getInstanceConfig(Argument::any()) - ->shouldBeCalled() - ->willReturn($info); - - $this->config->___setProperty('connection', $this->connection->reveal()); + $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 71cb7897e01c..f2220fd09554 100644 --- a/Spanner/tests/Snippet/InstanceTest.php +++ b/Spanner/tests/Snippet/InstanceTest.php @@ -17,21 +17,40 @@ namespace Google\Cloud\Spanner\Tests\Snippet; -use Google\Cloud\Core\Iam\Iam; +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\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\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\Tests\StubCreationTrait; +use Google\Cloud\Spanner\Serializer; +use Google\LongRunning\Client\OperationsClient; +use Google\LongRunning\Operation; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -43,7 +62,6 @@ class InstanceTest extends SnippetTestCase { use GrpcTestTrait; use ProphecyTrait; - use StubCreationTrait; const PROJECT = 'my-awesome-project'; const INSTANCE = 'my-instance'; @@ -51,26 +69,48 @@ class InstanceTest extends SnippetTestCase const BACKUP = 'my-backup'; const OPERATION = 'my-operation'; - private $connection; + private $spannerClient; + private $instanceAdminClient; + private $databaseAdminClient; + private $serializer; private $instance; + private $operationResponse; + private $page; + private $pagedListResponse; 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->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->spannerClient->reveal(), + $this->instanceAdminClient->reveal(), + $this->databaseAdminClient->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 +131,15 @@ 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->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testName() @@ -115,11 +156,12 @@ 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 InstanceProto(['node_count' => 1])); $res = $snippet->invoke(); $this->assertEquals('1', $res->output()); @@ -130,12 +172,13 @@ 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 InstanceProto(['name' => 'foo'])); +; $res = $snippet->invoke(); $this->assertEquals('Instance exists!', $res->output()); } @@ -145,11 +188,12 @@ 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 InstanceProto(['node_count' => 1])); $res = $snippet->invoke('info'); $info = $this->instance->info(); @@ -162,11 +206,12 @@ 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 InstanceProto(['state' => Instance::STATE_READY])); $res = $snippet->invoke(); $this->assertEquals('Instance is ready!', $res->output()); @@ -177,13 +222,13 @@ 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->operationResponse->reveal()); - $this->instance->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -192,10 +237,12 @@ public function testDelete() $snippet = $this->snippetFromMethod(Instance::class, 'delete'); $snippet->addLocal('instance', $this->instance); - $this->connection->deleteInstance(Argument::any()) - ->shouldBeCalled(); + $this->instanceAdminClient->deleteInstance( + Argument::type(DeleteInstanceRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce(); - $this->instance->___setProperty('connection', $this->connection->reveal()); $snippet->invoke(); } @@ -204,16 +251,15 @@ 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->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testCreateDatabaseFromBackup() @@ -223,16 +269,15 @@ 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->operationResponse->reveal()); $res = $snippet->invoke('operation'); - $this->assertInstanceOf(LongRunningOperation::class, $res->returnVal()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()); } public function testDatabase() @@ -250,17 +295,23 @@ public function testDatabases() $snippet = $this->snippetFromMethod(Instance::class, 'databases'); $snippet->addLocal('instance', $this->instance); - $this->connection->listDatabases(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'databases' => [ - [ - 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE) - ] - ] - ]); + $database = new DatabaseProto([ + 'name' => DatabaseAdminClient::databaseName( + self::PROJECT, + self::INSTANCE, + self::DATABASE + ) + ]); + + $this->page->getResponseObject() + ->willReturn(new ListDatabasesResponse(['databases' => [$database]])); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->listDatabases( + Argument::type(ListDatabasesRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); $res = $snippet->invoke('databases'); @@ -283,17 +334,23 @@ public function testBackups() $snippet = $this->snippetFromMethod(Instance::class, 'backups'); $snippet->addLocal('instance', $this->instance); - $this->connection->listBackups(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ - 'backups' => [ - [ - 'name' => DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP) - ] - ] - ]); + $backup = new BackupProto([ + 'name' => DatabaseAdminClient::backupName( + self::PROJECT, + self::INSTANCE, + self::BACKUP + ) + ]); - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->page->getResponseObject() + ->willReturn(new ListBackupsResponse(['backups' => [$backup]])); + + $this->databaseAdminClient->listBackups( + Argument::type(ListBackupsRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->pagedListResponse->reveal()); $res = $snippet->invoke('backups'); @@ -304,59 +361,63 @@ public function testBackups() public function testBackupOperations() { $backupOperationName = sprintf( - "%s/operations/%s", + '%s/operations/%s', DatabaseAdminClient::backupName(self::PROJECT, self::INSTANCE, self::BACKUP), 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); - $this->connection->listBackupOperations(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ - 'operations' => [ - [ - 'name' => $backupOperationName - ] - ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->listBackupOperations( + Argument::type(ListBackupOperationsRequest::class), + Argument::type('array') + ) + ->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(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 ); + $operation = new Operation(['name' => $databaseOperationName]); + $this->page->getResponseObject() + ->willReturn(new ListDatabaseOperationsResponse(['operations' => [$operation]])); + $snippet = $this->snippetFromMethod(Instance::class, 'databaseOperations'); $snippet->addLocal('instance', $this->instance); - $this->connection->listDatabaseOperations(Argument::any()) - ->shouldBeCalled() - ->WillReturn([ - 'operations' => [ - [ - 'name' => $databaseOperationName - ] - ] - ]); - - $this->instance->___setProperty('connection', $this->connection->reveal()); + $this->databaseAdminClient->listDatabaseOperations( + Argument::type(ListDatabaseOperationsRequest::class), + Argument::type('array') + ) + ->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(LongRunningOperation::class, $res->returnVal()->current()); + $this->assertInstanceOf(OperationResponse::class, $res->returnVal()->current()); } public function testIam() @@ -365,7 +426,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 +436,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 +445,27 @@ 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' - ] - ] - ]); + $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->instance->___setProperty('lroConnection', $lroConnection->reveal()); + $this->databaseAdminClient->getOperationsClient() + ->shouldBeCalledTimes(2) + ->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()); } 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..ed667d8e7e37 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; @@ -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/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..4104d8e3ba50 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,31 +33,30 @@ 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); - $this->snapshot = TestHelpers::stub(Snapshot::class, [ + $this->snapshot = new Snapshot( $operation->reveal(), $session->reveal(), [ 'id' => self::TRANSACTION, - 'readTimestamp' => new Timestamp(new \DateTime) + 'readTimestamp' => new Timestamp(new \DateTime()) ] - ], ['operation']); + ); } public function testClass() diff --git a/Spanner/tests/Snippet/SpannerClientTest.php b/Spanner/tests/Snippet/SpannerClientTest.php index 564f75698cc1..82285099dcc3 100644 --- a/Spanner/tests/Snippet/SpannerClientTest.php +++ b/Spanner/tests/Snippet/SpannerClientTest.php @@ -17,31 +17,31 @@ 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\Cloud\Spanner\Serializer; +use Google\Protobuf\Duration; /** * @group spanner @@ -49,22 +49,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 +89,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 +140,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 +175,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 +280,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..1ed99f178f28 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->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->serializer = new Serializer(); + $this->database = new Database( + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->reveal(), - [], self::PROJECT, self::DATABASE, $sessionPool->reveal() - ], ['operation']); + ); - $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,30 +110,23 @@ 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()); - + ); $snippet = $this->snippetFromClass(StructType::class); $snippet->replace('$database = $spanner->connect(\'my-instance\', \'my-database\');', ''); $snippet->addLocal('database', $this->database); @@ -177,7 +169,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..4ec903af8a3e 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->database = TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + $this->serializer = new Serializer(); + $this->database = new Database( + $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,38 @@ 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()); + ); $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..b30836d86a39 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() @@ -66,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() @@ -100,11 +98,13 @@ public function testClassReturnTransaction() public function testExecute() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGeneratorStream() + ); $snippet = $this->snippetFromMagicMethod(Transaction::class, 'execute'); $snippet->addLocal('transaction', $this->transaction); @@ -115,11 +115,13 @@ public function testExecute() public function testExecuteUpdate() { - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator(true)); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() + ->willReturn($this->resultGenerator(true) + ); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate'); $snippet->addLocal('transaction', $this->transaction); @@ -131,46 +133,40 @@ 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->refreshOperation($this->transaction, $this->connection->reveal()); + $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) + ); $snippet = $this->snippetFromMethod(Transaction::class, 'executeUpdate', 1); $snippet->addUse(Database::class); @@ -185,9 +181,10 @@ public function testExecuteUpdateWithStruct() public function testExecuteUpdateBatch() { - $this->connection->executeBatchDml(Argument::any()) - ->shouldBeCalled() - ->willReturn([ + $this->spannerClient->executeBatchDml( + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse([ 'resultSets' => [ [ 'stats' => [ @@ -195,9 +192,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 +210,23 @@ public function testExecuteUpdateBatch() public function testExecuteUpdateBatchError() { - $this->connection->executeBatchDml(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'resultSets' => [], - 'status' => [ + $this->spannerClient->executeBatchDml( + Argument::type(ExecuteBatchDmlRequest::class), + Argument::type('array') + )->willReturn(new ExecuteBatchDmlResponse([ + 'result_sets' => [], + 'status' => new 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 +237,17 @@ public function testExecuteUpdateBatchError() public function testRead() { - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator()); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGeneratorStream() + ); + + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMagicMethod(Transaction::class, 'read'); $snippet->addLocal('transaction', $this->transaction); @@ -256,7 +270,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 +282,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 +304,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 +316,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 +338,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 +350,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 +378,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 +390,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 +413,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 +427,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 +443,19 @@ public function testRollback() public function testCommit() { - $this->connection->commit(Argument::any()) - ->shouldBeCalled() - ->willReturn([ - 'commitTimestamp' => (new Timestamp(new \DateTime))->formatAsString() - ]); + $this->spannerClient->commit( + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]) + ] + )); - $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 +466,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(), - 'commitStats' => $expectedCommitStats, - ]); - - $this->refreshOperation($this->transaction, $this->connection->reveal()); + $this->spannerClient->commit( + Argument::type(CommitRequest::class), + Argument::type('array') + )->willReturn(new CommitResponse([ + 'commit_timestamp' => new TimestampProto(['seconds' => time()]), + 'commit_stats' => $expectedCommitStats, + ])); + + $this->refreshOperation( + $this->transaction, + $this->requestHandler->reveal(), + $this->serializer + ); $snippet = $this->snippetFromMethod(Transaction::class, 'getCommitStats'); $snippet->addLocal('transaction', $this->transaction); @@ -437,7 +501,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..32768588511c 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,13 +67,16 @@ 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); + $this->spannerClient = $this->prophesize(SpannerClient::class); } public function clientAndSnippetExecute() @@ -97,25 +96,31 @@ public function testExecute($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->executeStreamingSql(Argument::any()) - ->shouldBeCalled() + $this->spannerClient->executeStreamingSql( + Argument::type(ExecuteSqlRequest::class), + Argument::type('array') + ) + ->shouldBeCalledOnce() ->willReturn($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 +145,33 @@ 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] - ])); - - $this->refreshOperation($client, $this->connection->reveal()); + ], + 'values' => [null] + ]) + ); $snippet->addLocal($localName, $client); @@ -195,44 +196,40 @@ 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' => [[]] - ])); - - $this->refreshOperation($client, $this->connection->reveal()); + ], + 'values' => [[]] + ]) + ); $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,29 @@ 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()); + ); $snippet->addLocal($localName, $client); @@ -327,16 +328,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 +355,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 +419,10 @@ public function testRead($localName, $client, $snippet) { $this->checkAndSkipGrpcTests(); - $this->connection->streamingRead(Argument::any()) - ->shouldBeCalled() - ->willReturn($this->resultGenerator([ + $this->spannerClient->streamingRead( + Argument::type(ReadRequest::class), + Argument::type('array') + )->willReturn($this->resultGenerator([ 'metadata' => [ 'rowType' => [ 'fields' => [ @@ -412,9 +436,8 @@ public function testRead($localName, $client, $snippet) ] ], 'rows' => [0] - ])); - - $this->refreshOperation($client, $this->connection->reveal()); + ]) + ); $snippet->addLocal($localName, $client); @@ -435,63 +458,62 @@ private function setupDatabase() $sessionPool->setDatabase(Argument::any()) ->willReturn(null); - return \Google\Cloud\Core\Testing\TestHelpers::stub(Database::class, [ - $this->connection->reveal(), + return new Database( + $this->requestHandler->reveal(), + $this->serializer, $instance->reveal(), - $this->prophesize(LongRunningConnectionInterface::class)->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) + '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 ) ]); - return \Google\Cloud\Core\Testing\TestHelpers::stub(BatchSnapshot::class, [ - new Operation($this->connection->reveal(), 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/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..049b968c98fd 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); - - $this->database->___setProperty('connection', $this->connection->reveal()); + ->willReturn(new DatabaseProto($res)); - $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, @@ -1375,59 +1483,84 @@ public function testSessionPool() $this->assertInstanceOf(SessionPoolInterface::class, $this->database->sessionPool()); } - public function testClose() - { + 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); @@ -2055,32 +2318,36 @@ 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]] ); } 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 +2355,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 +2363,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 +2392,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 +2414,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..cd631d1f072a 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); + $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(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(['name' => SpannerClient::sessionName( - self::PROJECT, - self::INSTANCE, - self::DATABASE, - self::SESSION - )]); + ->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]); @@ -188,31 +256,31 @@ public function testDatabaseSingleUseSnapshotMinReadTimestampAndMaxStaleness($ch { $seconds = 1; $nanos = 2; + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); - $time = $this->parseTimeString($this->timestamp); - $timestamp = new Timestamp($time[0], $time[1]); - $duration = new Duration($seconds, $nanos); + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::withEntry('transaction', [ + $transaction = [ 'singleUse' => [ 'readOnly' => [ - 'minReadTimestamp' => $this->timestamp, - 'maxStaleness' => [ - 'seconds' => $seconds, - 'nanos' => $nanos - ] + 'minReadTimestamp' => $this->protoTimestamp->__debugInfo(), + '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, + 'minReadTimestamp' => $this->timestamp, 'maxStaleness' => $duration ]); @@ -223,19 +291,14 @@ 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(); - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $this->connection->executeStreamingSql(Argument::any()) - ->shouldNotbeCalled(); - - $database = $this->database($this->connection->reveal()); + $database = $this->database($this->spannerClient->reveal()); $snapshot = $database->snapshot([ - 'minReadTimestamp' => $timestamp, + 'minReadTimestamp' => $this->timestamp, ]); } @@ -245,16 +308,13 @@ public function testDatabasePreAllocatedSnapshotMaxStaleness() $seconds = 1; $nanos = 2; + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); - $duration = new Duration($seconds, $nanos); - - $this->connection->beginTransaction(Argument::any()) - ->shouldNotbeCalled(); - - $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 @@ -268,33 +328,32 @@ public function testDatabaseSnapshotSingleUseReadTimestampAndExactStaleness($chu { $seconds = 1; $nanos = 2; + $duration = new Duration(['seconds' => $seconds, 'nanos' => $nanos]); - $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 - ] - ] + $transaction = [ + 'singleUse' => [ + 'readOnly' => [ + 'readTimestamp' => $this->protoTimestamp->__debugInfo(), + 'exactStaleness' => $duration, ] - ]) - ))->shouldBeCalledTimes(1)->willReturn($this->resultGenerator($chunks)); + ] + ]; + + $this->spannerClient->beginTransaction(Argument::cetera())->shouldNotBeCalled(); - $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, - 'readTimestamp' => $timestamp, + 'readTimestamp' => $this->timestamp, 'exactStaleness' => $duration ]); @@ -309,33 +368,47 @@ public function testDatabaseSnapshotPreAllocateReadTimestampAndExactStaleness($c $seconds = 1; $nanos = 2; - $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->protoTimestamp->__debugInfo(), + '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, + 'readTimestamp' => $this->timestamp, 'exactStaleness' => $duration ]); @@ -347,18 +420,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 +453,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 +502,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 +532,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 +579,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->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([ 'returnReadTimestamp' => true @@ -480,13 +625,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 +647,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 +656,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 +665,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 +674,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 +683,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 +692,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 +701,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 +710,9 @@ public function testDatabaseReplaceBatchSingleUseReadWrite() public function testDatabaseDeleteSingleUseReadWrite() { - $this->connection->commit(Argument::withEntry('singleUseTransaction', [ - 'readWrite' => [] - ]))->shouldBeCalled()->willReturn([ - 'commitTimestamp' => $this->timestamp - ]); + $database = $this->createMockedCommitDatabase(); - $database = $this->database($this->connection->reveal()); - - $database->delete('Table', new KeySet); + $database->delete('Table', new KeySet()); } /** @@ -616,18 +720,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 +745,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 +774,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 +800,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 +825,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 +853,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 +876,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 +912,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",