Skip to content

Commit

Permalink
feature: add sort and order option to spanner:sessions (#67)
Browse files Browse the repository at this point in the history
* feature: add sort and order option to spanner:sessions

* get header dynamically

* add type

* use default values

* Update src/Console/SessionsCommand.php

Co-authored-by: Tomohito YABU <[email protected]>

* fix: better column specifying

* fix stan

* Apply suggestions from code review

Co-authored-by: Tomohito YABU <[email protected]>
Co-authored-by: halnique <[email protected]>

* test all sort patterns

* fixes

Co-authored-by: Tomohito YABU <[email protected]>
Co-authored-by: halnique <[email protected]>
  • Loading branch information
3 people authored Jan 17, 2023
1 parent 438ae63 commit f14e67a
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Added
- Support Schema\Builder::getAllTables()
- Command `spanner:cooldown` which clears all connections in the session pool.
- Command `spanner:warmup` now has a new option `--refresh` which will clear all existing sessions before warming up.
- Command `spanner:sessions` now has a new option `--sort` and `--order` which allows for sorting of results.

Changed
- Default SessionPool was changed from `Google\Cloud\Spanner\Session\CacheSessionPool` to `Colopl\Spanner\Session\CacheSessionPool` to patch an [unresolved issue on Google's end](https://github.com/googleapis/google-cloud-php/issues/5567).
Expand Down
80 changes: 64 additions & 16 deletions src/Console/SessionsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@

namespace Colopl\Spanner\Console;

use Colopl\Spanner\Connection;
use Colopl\Spanner\Connection as SpannerConnection;
use Colopl\Spanner\Session;
use Illuminate\Console\Command;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use RuntimeException;

class SessionsCommand extends Command
{
protected $signature = 'spanner:sessions {connections?* : The database connections to query}';
protected $signature = 'spanner:sessions {connections?* : The database connections to query}
{--sort=LastUsed : Name of column to be sorted [Name, Created, LastUsed]}
{--order=desc : Sort order as "asc" or "desc"}';

protected $description = 'List sessions on the server';

Expand All @@ -39,26 +46,67 @@ public function handle(DatabaseManager $db): void
static fn(string $name): bool => config("database.connections.{$name}.driver") === 'spanner',
);

$headers = ['Name', 'CreatedAt', 'LastUsedAt'];

foreach ($spannerConnectionNames as $name) {
$connection = $db->connection($name);
if ($connection instanceof SpannerConnection) {
$sessions = $connection->listSessions();
$count = count($sessions);
$data = [];
$this->info("{$connection->getName()} contains {$count} session(s).");
foreach ($sessions as $session) {
$data[] = [
$session->getName(),
$session->getCreatedAt(),
$session->getLastUsedAt(),
];
}
if (count($data) > 0) {
$this->table($headers, $data);
$sessions = $this->makeSessionData($connection);
$this->info("{$connection->getName()} contains {$sessions->count()} session(s).");
if ($sessions->isNotEmpty()) {
$headers = array_keys($sessions[0]);
$this->table($headers, $sessions);
}
}
}
}

/**
* @param Connection $connection
* @return Collection<int, array{ Name: string, Created: string, LastUsed: string }>
*/
protected function makeSessionData(Connection $connection): Collection
{
$descending = $this->getOrder() === 'desc';

return $connection->listSessions()
->sortBy(fn(Session $s) => $this->getSortValue($s), descending: $descending)
->map(static fn(Session $s) => [
'Name' => $s->getName(),
'Created' => (string) $s->getCreatedAt(),
'LastUsed' => (string) $s->getLastUsedAt(),
]);
}

/**
* @param Session $session
* @return string
*/
protected function getSortValue(Session $session): string
{
$sort = $this->option('sort');
assert(is_string($sort));
return match (Str::studly($sort)) {
'Name' => $session->getName(),
'Created' => (string) $session->getCreatedAt(),
'LastUsed' => (string) $session->getLastUsedAt(),
default => throw new RuntimeException("Unknown column: {$sort}"),
};
}

/**
* @return string
*/
protected function getOrder(): string
{
$order = $this->option('order');
assert(is_string($order));

$order = strtolower($order);

if (!in_array($order, ['asc', 'desc'], true)) {
throw new RuntimeException("Unknown order: {$order}. Must be [ASC, DESC]");
}

return $order;
}

}
88 changes: 88 additions & 0 deletions tests/Console/SessionsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,28 @@
namespace Colopl\Spanner\Tests\Console;

use Colopl\Spanner\Connection;
use Colopl\Spanner\Session as SessionInfo;
use Google\Cloud\Spanner\Session\Session;
use Google\Cloud\Spanner\Session\SessionPoolInterface;
use RuntimeException;

class SessionsCommandTest extends TestCase
{
/**
* @param Connection $connection
* @param int $amount
* @return list<Session>
*/
protected function createSessions(Connection $connection, int $amount): array
{
$pool = $connection->getSpannerDatabase()->sessionPool() ?? throw new RuntimeException('unreachable');
$sessions = [];
for ($i = 0; $i < $amount; $i++) {
$sessions[] = $pool->acquire(SessionPoolInterface::CONTEXT_READ);
}
return $sessions;
}

public function test_no_args(): void
{
if (getenv('SPANNER_EMULATOR_HOST')) {
Expand Down Expand Up @@ -80,4 +99,73 @@ public function test_no_sessions_shows_no_table(): void
->assertSuccessful()
->run();
}

public function test_sort(): void
{
if (getenv('SPANNER_EMULATOR_HOST')) {
$this->markTestSkipped('Cannot list sessions on emulator');
}

$conn = $this->getDefaultConnection();
$this->setUpDatabaseOnce($conn);
$this->createSessions($conn, 2);

$list = $conn->listSessions()
->sortByDesc(fn(SessionInfo $s) => $s->getName())
->map(static fn(SessionInfo $s) => [
$s->getName(),
$s->getCreatedAt()->format('Y-m-d H:i:s'),
$s->getLastUsedAt()->format('Y-m-d H:i:s'),
]);

$this->artisan('spanner:sessions', ['connections' => 'main', '--sort' => 'name'])
->expectsOutput('main contains 2 session(s).')
->expectsTable(['Name', 'Created', 'LastUsed'], $list)
->assertSuccessful()
->run();
}

public function test_sort_order(): void
{
if (getenv('SPANNER_EMULATOR_HOST')) {
$this->markTestSkipped('Cannot list sessions on emulator');
}

$conn = $this->getDefaultConnection();
$this->setUpDatabaseOnce($conn);
$this->createSessions($conn, 2);

$list = $conn->listSessions()
->sortBy(fn(SessionInfo $s) => $s->getName())
->map(static fn(SessionInfo $s) => [
$s->getName(),
$s->getCreatedAt()->format('Y-m-d H:i:s'),
$s->getLastUsedAt()->format('Y-m-d H:i:s'),
]);

$this->artisan('spanner:sessions', ['connections' => 'main', '--sort' => 'name', '--order' => 'asc'])
->expectsOutput('main contains 2 session(s).')
->expectsTable(['Name', 'Created', 'LastUsed'], $list)
->assertSuccessful()
->run();
}

public function test_sort_patterns(): void
{
if (getenv('SPANNER_EMULATOR_HOST')) {
$this->markTestSkipped('Cannot list sessions on emulator');
}

$conn = $this->getDefaultConnection();
$this->setUpDatabaseOnce($conn);
$this->createSessions($conn, 1);

foreach (['Name', 'Created', 'LastUsed'] as $column) {
foreach (['desc', 'asc'] as $order) {
$this->artisan('spanner:sessions', ['connections' => 'main', '--sort' => $column, '--order' => $order])
->assertSuccessful()
->run();
}
}
}
}

0 comments on commit f14e67a

Please sign in to comment.