Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ea4cdcb
feat!: owlbot updates for Spanner V2
bshaffer Nov 16, 2024
6fca00b
update gencode, cleanup deprecated ones
bshaffer May 12, 2025
d9eb87d
Merge branch 'main' into spanner-v2-owlbot-updates
bshaffer Aug 22, 2025
c823b6e
googleapis updates
bshaffer Aug 22, 2025
c24bfd0
feat!: Spanner V2
bshaffer Nov 16, 2024
5887aef
remove old clients
bshaffer May 12, 2025
e74b497
untange options vars and traits by
bshaffer May 13, 2025
84034cc
types cleanup
bshaffer Jul 2, 2025
a5c7c74
add validateOptions helper method
bshaffer Jul 23, 2025
05cdfb4
add validateOptions for options arrays
bshaffer Jul 23, 2025
db5b049
feat(Spanner): multiplexed sessions
bshaffer May 12, 2025
42f8877
unit and snippet test fixes
bshaffer Sep 2, 2025
cd49481
use validateOptions instead of splitOptionalArgs
bshaffer Sep 3, 2025
e0e5ffd
fix tests
bshaffer Sep 5, 2025
9b5b921
whitespace fix
bshaffer Sep 5, 2025
0c87817
Merge branch 'spanner-v2-refactor' into spanner-multiplexed-sessions
bshaffer Sep 5, 2025
b3d69b6
add support for mutationkey in beginTransactionRequest
bshaffer Sep 8, 2025
84142b2
remove precommit token from SessionCache
bshaffer Sep 9, 2025
0718124
remove unused imports
bshaffer Sep 9, 2025
6856c51
Adding test requirements, refactoring
bshaffer Sep 11, 2025
d64375d
tests for Locking
bshaffer Sep 22, 2025
303b300
logic for Precommit tokens, tests for Cache Expiration
bshaffer Sep 23, 2025
a7f4d1d
finish test coverage
bshaffer Sep 23, 2025
0fbc67c
fix tests
bshaffer Sep 24, 2025
c76fb31
more test fixes
bshaffer Sep 24, 2025
e74f1ec
fix more tests
bshaffer Sep 24, 2025
08fdd7e
how many times will I try to fix the tests you ask? as many times as …
bshaffer Sep 24, 2025
eb53931
fix final failing test maybe
bshaffer Sep 25, 2025
8e1a649
refactor SessionCache a tiny smidgeon
bshaffer Sep 25, 2025
93ecc65
fix windows tests and one emulator test
bshaffer Sep 25, 2025
5ece1e7
increment retryLimit
bshaffer Sep 26, 2025
a8a68b4
remove verbose flag
bshaffer Sep 29, 2025
aa7d25d
fix emulator tests
bshaffer Sep 30, 2025
db281f5
Merge branch 'main' into spanner-v2-owlbot-updates
bshaffer Sep 30, 2025
feb9061
add back spannerclient
bshaffer Sep 30, 2025
15e96e0
Merge branch 'spanner-v2-owlbot-updates' into spanner-v2-refactor
bshaffer Sep 30, 2025
7992b4f
feat(Core): add OptionsValidator
bshaffer Sep 30, 2025
0727e07
cleanup from code review and tests
bshaffer Sep 30, 2025
fb4467a
set google/cloud-core dependency to next release
bshaffer Sep 30, 2025
0870201
Merge branch 'spanner-v2-refactor' into spanner-multiplexed-sessions
bshaffer Sep 30, 2025
3993376
fix opis tests
bshaffer Sep 30, 2025
e0bf020
fix from merge
bshaffer Sep 30, 2025
7d2223b
remove unused constant
bshaffer Oct 1, 2025
c6afb75
consistency with constructor options
bshaffer Oct 1, 2025
f4ef619
improved README.md / MIGRATIING.md
bshaffer Oct 1, 2025
c418d24
add validateOptions for all remaining instance of splitOptionalArgs
bshaffer Oct 2, 2025
549b6ea
cleanup WIP
bshaffer Oct 7, 2025
0b1ed7c
Transaction Options WIP
bshaffer Oct 8, 2025
9d2bdc5
style fixes
bshaffer Oct 10, 2025
43f2826
misc cleanup
bshaffer Oct 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .github/workflows/emulator-system-tests-spanner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ jobs:
php-version: '8.1'
ini-values: grpc.enable_fork_support=1
tools: pecl
extensions: bcmath, grpc
extensions: bcmath, grpc, pcntl

- 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"}},"canonical":false}' -d Spanner
composer update --prefer-dist --no-interaction --no-suggest -d Spanner/

- name: Run system tests
run: |
Spanner/vendor/bin/phpunit -c Spanner/phpunit-system.xml.dist
Spanner/vendor/bin/phpunit -c Spanner/phpunit-system.xml.dist --testdox
env:
SPANNER_EMULATOR_HOST: localhost:9010
GOOGLE_CLOUD_PHP_TESTS_KEY_PATH: '.github/emulator/example-key.json'
Expand Down
41 changes: 5 additions & 36 deletions Core/src/ApiHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
*/
namespace Google\Cloud\Core;

use Exception;
use Google\ApiCore\ArrayTrait;
use Google\ApiCore\Options\CallOptions;
use Google\Protobuf\Internal\Message;
use Google\Protobuf\NullValue;
use LogicException;

/**
* @internal
Expand All @@ -33,6 +31,8 @@ trait ApiHelperTrait
use ArrayTrait;
use TimeTrait;

private OptionsValidator $optionsValidator;

/**
* Format a struct for the API.
*
Expand Down Expand Up @@ -276,40 +276,9 @@ private function splitOptionalArgs(array $input, array $extraAllowedKeys = []):
*/
private function validateOptions(array $options, array|Message|string ...$optionTypes): array
{
$splitOptions = [];
foreach ($optionTypes as $optionType) {
if (is_array($optionType)) {
$splitOptions[] = $this->pluckArray($optionType, $options);
} elseif ($optionType === CallOptions::class) {
$callOptionKeys = array_keys((new CallOptions([]))->toArray());
$splitOptions[] = $this->pluckArray($callOptionKeys, $options);
} elseif ($optionType instanceof Message) {
$messageKeys = array_map(
fn ($method) => lcfirst(substr($method, 3)),
array_filter(
get_class_methods($optionType),
fn ($m) => 0 === strpos($m, 'get')
)
);
$messageOptions = $this->pluckArray($messageKeys, $options);
if ($optionType instanceof Message) {
$optionType->mergeFromJsonString(json_encode($messageOptions, JSON_FORCE_OBJECT));
$validatedOptionGroup = $optionType;
} else {
$validatedOptionGroup = $messageOptions;
}
$splitOptions[] = $validatedOptionGroup;
} else {
throw new LogicException(sprintf('Invalid option type: %s', $optionType));
}
if (!isset($this->optionsValidator)) {
$this->optionsValidator = new OptionsValidator();
}

if (!empty($options)) {
throw new Exception(
'Unexpected option(s) provided: ' . implode(', ', array_keys($options))
);
}

return $splitOptions;
return $this->optionsValidator->validateOptions($options, ...$optionTypes);
}
}
2 changes: 1 addition & 1 deletion Core/src/Iam/Iam.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
119 changes: 119 additions & 0 deletions Core/src/LongRunning/LongRunningClientConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php
/**
* Copyright 2025 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Core\LongRunning;

use Google\ApiCore\OperationResponse;
use Google\ApiCore\Serializer;
use Google\Cloud\Core\RequestProcessorTrait;
use Google\LongRunning\ListOperationsRequest;
use Google\Protobuf\Any;

/**
* Defines the calls required to manage Long Running Operations using a GAPIC
* generated client.
*
* @internal
*/
class LongRunningClientConnection implements LongRunningConnectionInterface
{
use RequestProcessorTrait;

public function __construct(
private $gapicClient,
private Serializer $serializer
) {
}

/**
* @param array $args
*/
public function get(array $args)
{
$operationResponse = $this->gapicClient->resumeOperation($args['name']);

return $this->operationResponseToArray($operationResponse);
}

/**
* @param array $args
*/
public function cancel(array $args)
{
$operationResponse = $this->gapicClient->resumeOperation(
$args['name'],
$args['method'] ?? null
);
$operationResponse->cancel();

return $this->operationResponseToArray($operationResponse);
}

/**
* @param array $args
*/
public function delete(array $args)
{
$operationResponse = $this->gapicClient->resumeOperation(
$args['name'],
$args['method'] ?? null
);
$operationResponse->cancel();

return $this->operationResponseToArray($operationResponse);
}

/**
* @param array $args
*/
public function operations(array $args)
{
$request = ListOperationsRequest::build($args['name'], $args['filter'] ?? null);
$response = $this->gapicClient->getOperationsClient()->listOperations($request);

return $this->handleResponse($response);
}

private function operationResponseToArray(OperationResponse $operationResponse)
{
$response = $this->handleResponse($operationResponse->getLastProtoResponse());
$metaType = $response['metadata']['typeUrl'];

// unpack result Any type
$result = $operationResponse->getResult();
if ($result instanceof Any) {
// For some reason we aren't doing this in GAX OperationResponse (but we should)
$result = $result->unpack();
}
$response['response'] = $this->handleResponse($result);

// unpack error Any type
$response['error'] = $this->handleResponse($operationResponse->getError());

$metadata = $operationResponse->getMetadata();
if ($metadata instanceof Any) {
// For some reason we aren't doing this in GAX OperationResponse (but we should)
$metadata = $metadata->unpack();
}
$response['metadata'] = $this->handleResponse($metadata);

// Used in LongRunningOperation to invoke callables
$response['metadata'] += ['typeUrl' => $metaType];

return $response;
}
}
10 changes: 5 additions & 5 deletions Core/src/LongRunning/LongRunningOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

/**
* Represent and interact with a Long Running Operation.
* @template T
*/
class LongRunningOperation
{
Expand Down Expand Up @@ -180,7 +181,7 @@ public function state(array $options = [])
* ```
*
* @param array $options [optional] Configuration options.
* @return mixed|null
* @return T|mixed|null
*/
public function result(array $options = [])
{
Expand Down Expand Up @@ -252,12 +253,11 @@ public function reload(array $options = [])

$this->result = null;
$this->error = null;
if (isset($res['done']) && $res['done']) {

if ($res['done'] ?? false && isset($res['metadata']['typeUrl'])) {
$type = $res['metadata']['typeUrl'];
$this->result = $this->executeDoneCallback($type, $res['response']);
$this->error = (isset($res['error']))
? $res['error']
: null;
$this->error = $res['error'] ?? null;
}

return $this->info = $res;
Expand Down
77 changes: 77 additions & 0 deletions Core/src/OptionsValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/**
* Copyright 2025 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\Cloud\Core;

use Google\ApiCore\ArrayTrait;
use Google\ApiCore\Options\CallOptions;
use Google\ApiCore\Serializer;
use Google\Protobuf\Internal\Message;
use LogicException;

class OptionsValidator
{
use ArrayTrait;

public function __construct(private ?Serializer $serializer = null)
{
}

/**
* Helper method used to validate optons based on the supplied $optionTypes
* $optionTypes can be an array of string keys, a protobuf Message classname, or a
* the CallOptions classname. Parameters are split and returned in the order
* that the options types are provided.
*/
public function validateOptions(array $options, array|Message|string ...$optionTypes): array
{
$splitOptions = [];
foreach ($optionTypes as $optionType) {
if (is_array($optionType)) {
$splitOptions[] = $this->pluckArray($optionType, $options);
} elseif ($optionType === CallOptions::class) {
$callOptionKeys = array_keys((new CallOptions([]))->toArray());
$splitOptions[] = $this->pluckArray($callOptionKeys, $options);
} elseif ($optionType instanceof Message) {
$messageKeys = array_map(
fn ($method) => lcfirst(substr($method, 3)),
array_filter(
get_class_methods($optionType),
fn ($m) => 0 === strpos($m, 'get')
)
);
$messageOptions = $this->pluckArray($messageKeys, $options);
if ($this->serializer) {
$optionType = $this->serializer->decodeMessage($optionType, $messageOptions);
} else {
$optionType->mergeFromJsonString(json_encode($messageOptions, JSON_FORCE_OBJECT));
}
$splitOptions[] = $optionType;
} else {
throw new LogicException(sprintf('Invalid option type: %s', $optionType));
}
}

if (!empty($options)) {
throw new LogicException(
'Unexpected option(s) provided: ' . implode(', ', array_keys($options))
);
}

return $splitOptions;
}
}
15 changes: 9 additions & 6 deletions Core/src/RequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ class RequestHandler
*/
private Serializer $serializer;

private array $clients;
private array $clients = [];

/**
* @param Serializer $serializer
* @param array $clientClasses
* @param array<string|object> $clients
* @param array $clientConfig
*/
public function __construct(
Serializer $serializer,
array $clientClasses,
array $clients,
array $clientConfig = []
) {
//@codeCoverageIgnoreStart
Expand All @@ -75,9 +75,12 @@ 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 ($clients as $client) {
if (is_object($client)) {
$this->clients[get_class($client)] = $client;
} else {
$this->clients[$client] = new $client($clientConfig);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Core/src/ServiceBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public function pubsub(array $config = [])
*
* Example:
* ```
* $spanner = $cloud->spanner();
* $spanner = $cloud->spanner(['projectId' => 'my-project']);
* ```
*
* @param array $config [optional] {
Expand Down
2 changes: 2 additions & 0 deletions Core/src/Testing/Snippet/SnippetTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
*/
class SnippetTestCase extends TestCase
{
const PROJECT = 'my-awesome-project';

use CheckForClassTrait;

private static $coverage;
Expand Down
Loading
Loading