Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Spanner): Support FGAC #5642

Merged
merged 10 commits into from
Dec 14, 2022
21 changes: 19 additions & 2 deletions Spanner/src/Batch/BatchClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ class BatchClient
*/
private $databaseName;

/**
* @var string
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
*/
private $databaseRole;

/**
* @var array
*/
Expand All @@ -131,11 +136,17 @@ class BatchClient
* @param Operation $operation A Cloud Spanner Operations wrapper.
* @param string $databaseName The database name to which the batch client
* instance is scoped.
* @param array $options [optional] {
* Configuration options.
*
* @type string $databaseRole The user created database role which creates the session.
* }
*/
public function __construct(Operation $operation, $databaseName)
public function __construct(Operation $operation, $databaseName, array $options = [])
{
$this->operation = $operation;
$this->databaseName = $databaseName;
$this->databaseRole = isset($options['databaseRole']) ? $options['databaseRole'] : null;
}

/**
Expand Down Expand Up @@ -168,6 +179,8 @@ public function snapshot(array $options = [])
'transactionOptions' => [],
];

$sessionOptions = $this->pluck('sessionOptions', $options, false) ?: [];

// Single Use transactions are not supported in batch mode.
$options['transactionOptions']['singleUse'] = false;

Expand All @@ -176,9 +189,13 @@ public function snapshot(array $options = [])

$transactionOptions = $this->configureSnapshotOptions($transactionOptions);

if ($this->databaseRole !== null) {
$sessionOptions['creator_role'] = $this->databaseRole;
}

$session = $this->operation->createSession(
$this->databaseName,
$this->pluck('sessionOptions', $options, false) ?: []
$sessionOptions
);

return $this->operation->snapshot($session, [
Expand Down
10 changes: 9 additions & 1 deletion Spanner/src/Connection/Grpc.php
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,15 @@ public function createSession(array $args)

$session = $this->pluck('session', $args, false);
if ($session) {
$args['session'] = $this->serializer->decodeMessage(new Session, $session);
$args['session'] = $this->serializer->decodeMessage(
new Session,
array_filter(
$session,
function ($value) {
return !is_null($value);
}
)
);
}

return $this->send([$this->spannerClient, 'createSession'], [
Expand Down
14 changes: 13 additions & 1 deletion Spanner/src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ class Database
*/
private $isRunningTransaction = false;

/**
* @var string
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
*/
private $databaseRole;

/**
* Create an object representing a Database.
*
Expand All @@ -185,6 +190,7 @@ class Database
* @param bool $returnInt64AsObject [optional If true, 64 bit integers will
* 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.
*/
public function __construct(
ConnectionInterface $connection,
Expand All @@ -195,7 +201,8 @@ public function __construct(
$name,
SessionPoolInterface $sessionPool = null,
$returnInt64AsObject = false,
array $info = []
array $info = [],
$databaseRole = null
) {
$this->connection = $connection;
$this->instance = $instance;
Expand All @@ -210,6 +217,7 @@ public function __construct(
}

$this->setLroProperties($lroConnection, $lroCallables, $this->name);
$this->databaseRole = $databaseRole;
}

/**
Expand Down Expand Up @@ -2023,6 +2031,10 @@ private function selectSession($context = SessionPoolInterface::CONTEXT_READ, ar
return $this->session = $this->sessionPool->acquire($context);
}

if ($this->databaseRole !== null) {
$options['creator_role'] = $this->databaseRole;
}

return $this->session = $this->operation->createSession($this->name, $options);
}

Expand Down
10 changes: 9 additions & 1 deletion Spanner/src/Instance.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,19 @@ public function createDatabaseFromBackup($name, $backup, array $options = [])
* $database = $instance->database('my-database');
* ```
*
* Database role configured on the database object
* will be applied to the session created by this object.
* ```
* $database = $instance->database('my-database', ['databaseRole' => 'Reader']);
* ```
*
* @param string $name The database name
* @param array $options [optional] {
* Configuration options.
*
* @type SessionPoolInterface $sessionPool A pool used to manage
* sessions.
* @type string $databaseRole The user created database role which creates the session.
* }
* @return Database
*/
Expand All @@ -502,7 +509,8 @@ public function database($name, array $options = [])
$name,
isset($options['sessionPool']) ? $options['sessionPool'] : null,
$this->returnInt64AsObject,
isset($options['database']) ? $options['database'] : []
isset($options['database']) ? $options['database'] : [],
isset($options['databaseRole']) ? $options['databaseRole'] : null
);
}

Expand Down
4 changes: 3 additions & 1 deletion Spanner/src/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ public function createSnapshot(Session $session, array $res = [], $className = S
* labels can be associated with a given session. See
* https://goo.gl/xmQnxf for more information on and examples of
* labels.
* @type string $creator_role The user created database role which creates the session.
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
* }
* @return Session
*/
Expand All @@ -561,7 +562,8 @@ public function createSession($databaseName, array $options = [])
$res = $this->connection->createSession([
'database' => $databaseName,
'session' => [
'labels' => $this->pluck('labels', $options, false) ?: []
'labels' => $this->pluck('labels', $options, false) ?: [],
'creator_role' => $this->pluck('creator_role', $options, false) ?: null
vishwarajanand marked this conversation as resolved.
Show resolved Hide resolved
]
] + $options);

Expand Down
22 changes: 21 additions & 1 deletion Spanner/src/Session/CacheSessionPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,23 @@
* ]
* ]);
* ```
*
* Database role configured on the pool will be applied to each session created by the pool.
* ```
* use Google\Cloud\Spanner\SpannerClient;
* use Google\Cloud\Spanner\Session\CacheSessionPool;
* use Symfony\Component\Cache\Adapter\FilesystemAdapter;
*
* $spanner = new SpannerClient();
* $cache = new FilesystemAdapter();
* $sessionPool = new CacheSessionPool($cache, [
* 'databaseRole' => 'Reader'
* ]);
*
* $database = $spanner->connect('my-instance', 'my-database', [
* 'sessionPool' => $sessionPool
* ]);
* ```
*/
class CacheSessionPool implements SessionPoolInterface
{
Expand Down Expand Up @@ -191,6 +208,8 @@ class CacheSessionPool implements SessionPoolInterface
* labels can be associated with a given session. See
* https://goo.gl/xmQnxf for more information on and examples of
* labels.
* @type string $databaseRole The user created database role which creates the session.
* }
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
* }
* @throws \InvalidArgumentException
*/
Expand Down Expand Up @@ -660,7 +679,8 @@ private function createSessions($count)
$res = $this->database->connection()->batchCreateSessions([
'database' => $this->database->name(),
'sessionTemplate' => [
'labels' => isset($this->config['labels']) ? $this->config['labels'] : []
'labels' => isset($this->config['labels']) ? $this->config['labels'] : [],
'creator_role' => isset($this->config['databaseRole']) ? $this->config['databaseRole'] : null
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
],
'sessionCount' => $count - $created
]);
Expand Down
24 changes: 22 additions & 2 deletions Spanner/src/SpannerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,22 @@ public function __construct(array $config = [])
* $batch = $spanner->batch('instance-id', 'database-id');
* ```
*
* Database role configured in the optional $options array
* will be applied to the session created by this object.
* ```
* $batch = $spanner->batch('instance-id', 'database-id', ['databaseRole' => 'Reader']);
* ```
*
* @param string $instanceId The instance to connect to.
* @param string $databaseId The database to connect to.
* @param array $options [optional] {
* Configuration options.
*
* @type string $databaseRole The user created database role which creates the session.
* }
* @return BatchClient
*/
public function batch($instanceId, $databaseId)
public function batch($instanceId, $databaseId, array $options = [])
{
$operation = new Operation(
$this->connection,
Expand All @@ -269,7 +280,8 @@ public function batch($instanceId, $databaseId)
$this->projectId,
$instanceId,
$databaseId
)
),
$options
);
}

Expand Down Expand Up @@ -566,6 +578,14 @@ function (array $instance) {
* $database = $spanner->connect('instance-id', 'database-id');
* ```
*
* Database role configured on the $options parameter
* will be applied to the session created by this object.
* Note: When the databseRole and sessionPool both are present in the options,
* we prioritize the sessionPool.
* ```
* $database = $spanner->connect('instance-id', 'database-id', ['databaseRole' => 'Reader']);
vishwarajanand marked this conversation as resolved.
Show resolved Hide resolved
* ```
*
ajupazhamayil marked this conversation as resolved.
Show resolved Hide resolved
* @param Instance|string $instance The instance object or instance name.
* @param string $name The database name.
* @param array $options [optional] {
Expand Down
10 changes: 10 additions & 0 deletions Spanner/tests/Snippet/InstanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,14 @@ public function testLongRunningOperations()
$this->assertInstanceOf(ItemIterator::class, $res->returnVal());
$this->assertContainsOnlyInstancesOf(LongRunningOperation::class, $res->returnVal());
}

public function testDatabaseWithDatabaseRole()
{
$snippet = $this->snippetFromMethod(Instance::class, 'database', 1);
$snippet->addLocal('instance', $this->instance);

$res = $snippet->invoke('database');
$this->assertInstanceOf(Database::class, $res->returnVal());
$this->assertEquals(self::DATABASE, DatabaseAdminClient::parseName($res->returnVal()->name())['database']);
}
}
13 changes: 13 additions & 0 deletions Spanner/tests/Snippet/Session/CacheSessionPoolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,17 @@ public function testClassLabels()
$snippet->addLocal('cache', new MemoryCacheItemPool);
$res = $snippet->invoke();
}

public function testClassWithDatabaseRole()
{
if (!extension_loaded('grpc')) {
$this->markTestSkipped('Must have the grpc extension installed to run this test.');
}

$snippet = $this->snippetFromClass(CacheSessionPool::class, 2);
$snippet->replace('$cache =', '//$cache =');
$snippet->addLocal('cache', new MemoryCacheItemPool);
$res = $snippet->invoke('database');
$this->assertInstanceOf(Database::class, $res->returnVal());
}
}
17 changes: 17 additions & 0 deletions Spanner/tests/Snippet/SpannerClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,21 @@ public function testEmulator()
$this->assertInstanceOf(SpannerClient::class, $res->returnVal());
$this->assertEquals('localhost:9010', getenv('SPANNER_EMULATOR_HOST'));
}

public function testConnectWithDatabaseRole()
{
$snippet = $this->snippetFromMethod(SpannerClient::class, 'connect', 1);
$snippet->addLocal('spanner', $this->client);

$res = $snippet->invoke('database');
$this->assertInstanceOf(Database::class, $res->returnVal());
}

public function testBatchWithDatabaseRole()
{
$snippet = $this->snippetFromMethod(SpannerClient::class, 'batch', 1);
$snippet->addLocal('spanner', $this->client);
$res = $snippet->invoke('batch');
$this->assertInstanceOf(BatchClient::class, $res->returnVal());
}
}
Loading