Skip to content

Commit

Permalink
feat: add Connection methods selectWithOptions and cursorWithOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
taka-oyama committed Jul 19, 2023
1 parent 98bba00 commit 3bcd862
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 131 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# v6.0.0 [Not released Yet]

Added
- Deprecation warnings to `Connection`'s methods `cursorWithTimestampBound` `selectWithTimestampBound` `selectOneWithTimestampBound`. Use `cursorWithOptions` `selectWithOptions` instead. (#122)
- `Connection` has new methods `selectWithOptions` `cursorWithOptions` which allows spanner specific options to be set for each query. (#122)

Changed
- [Breaking] Match `Query\Builder::forceIndex()` behavior with laravel's (`forceIndex` property no longer exists). (#114)

Expand Down
32 changes: 9 additions & 23 deletions src/Concerns/ManagesStaleReads.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,60 +19,46 @@

use Colopl\Spanner\TimestampBound\TimestampBoundInterface;
use Generator;
use Throwable;

/**
* @deprecated This trait will be removed in v7.
*/
trait ManagesStaleReads
{
/**
* @deprecated use selectWithOptions() instead. This method will be removed in v7.
* @param string $query
* @param array<string, mixed> $bindings
* @param TimestampBoundInterface|null $timestampBound
* @return Generator<int, list<mixed>|null>
*/
public function cursorWithTimestampBound($query, $bindings = [], TimestampBoundInterface $timestampBound = null): Generator
{
return $this->run($query, $bindings, function ($query, $bindings) use ($timestampBound) {
if ($this->pretending()) {
return call_user_func(function() {
yield from [];
});
}

$options = ['parameters' => $this->prepareBindings($bindings)];
if ($timestampBound) {
$options = array_merge($options, $timestampBound->transactionOptions());
}

return $this->getSpannerDatabase()
->execute($query, $options)
->rows();
});
return $this->cursorWithOptions($query, $bindings, $timestampBound?->transactionOptions() ?? []);
}

/**
* @deprecated use selectWithOptions() instead. This method will be removed in v7.
* @param string $query
* @param array $bindings
* @param TimestampBoundInterface|null $timestampBound
* @return array
*/
public function selectWithTimestampBound($query, $bindings = [], TimestampBoundInterface $timestampBound = null): array
{
return $this->withSessionNotFoundHandling(function () use ($query, $bindings, $timestampBound) {
return iterator_to_array($this->cursorWithTimestampBound($query, $bindings, $timestampBound));
});
return $this->selectWithOptions($query, $bindings, $timestampBound?->transactionOptions() ?? []);
}

/**
* @deprecated use selectWithOptions() instead. This method will be removed in v7.
* @param string $query
* @param array<mixed> $bindings
* @param TimestampBoundInterface|null $timestampBound
* @return array<mixed>|null
*/
public function selectOneWithTimestampBound($query, $bindings = [], TimestampBoundInterface $timestampBound = null): ?array
{
return $this->withSessionNotFoundHandling(function () use ($query, $bindings, $timestampBound) {
return $this->cursorWithTimestampBound($query, $bindings, $timestampBound)->current();
});
return $this->cursorWithTimestampBound($query, $bindings, $timestampBound)->current();
}
}

63 changes: 45 additions & 18 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,32 +256,44 @@ public function query(): QueryBuilder
*/
public function select($query, $bindings = [], $useReadPdo = true): array
{
return $this->run($query, $bindings, function ($query, $bindings) {
if ($this->pretending()) {
return [];
}

$generator = $this->getDatabaseContext()
->execute($query, ['parameters' => $this->prepareBindings($bindings)])
->rows();

return iterator_to_array($generator);
});
return $this->selectWithOptions($query, $bindings, []);
}

/**
* @inheritDoc
*/
public function cursor($query, $bindings = [], $useReadPdo = true): Generator
{
return $this->run($query, $bindings, function ($query, $bindings) {
if ($this->pretending()) {
return call_user_func(function() { yield from []; });
}
return $this->cursorWithOptions($query, $bindings, []);
}

return $this->getDatabaseContext()
->execute($query, ['parameters' => $this->prepareBindings($bindings)])
->rows();
/**
* @param string $query
* @param array<array-key, mixed> $bindings
* @param array<string, mixed> $options
* @return array<int, array<array-key, mixed>>
*/
public function selectWithOptions(string $query, array $bindings, array $options): array
{
return $this->run($query, $bindings, function ($query, $bindings) use ($options): array {
return !$this->pretending()
? iterator_to_array($this->executeQuery($query, $bindings, $options))
: [];
});
}

/**
* @param string $query
* @param array<array-key, mixed> $bindings
* @param array<string, mixed> $options
* @return Generator<int, array<array-key, mixed>>
*/
public function cursorWithOptions(string $query, array $bindings, array $options): Generator
{
return $this->run($query, $bindings, function ($query, $bindings) use ($options): Generator {
return !$this->pretending()
? $this->executeQuery($query, $bindings, $options)
: (static fn() => yield from [])();
});
}

Expand Down Expand Up @@ -522,6 +534,21 @@ protected function withSessionNotFoundHandling(Closure $callback): mixed
}
}

/**
* @param string $query
* @param array<array-key, mixed> $bindings
* @param array<string, mixed> $options
* @return Generator<int, array<array-key, mixed>>
*/
protected function executeQuery(string $query, array $bindings, array $options): Generator
{
$options += ['parameters' => $this->prepareBindings($bindings)];

return $this->getDatabaseContext()
->execute($query, $options)
->rows();
}

/**
* Check if this is "session not found" error
*
Expand Down
12 changes: 8 additions & 4 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,16 @@ protected function prepareInsertForDml($values)
*/
protected function runSelect()
{
$sql = $this->toSql();
$bindings = $this->getBindings();
$options = [];

if ($this->timestampBound !== null) {
return $this->connection->selectWithTimestampBound(
$this->toSql(), $this->getBindings(), $this->timestampBound
);
$options += $this->timestampBound->transactionOptions();
}

return parent::runSelect();
return count($options) > 0
? $this->connection->selectWithOptions($sql, $bindings, $options)
: $this->connection->select($sql, $bindings);
}
}
106 changes: 104 additions & 2 deletions tests/ConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
use Colopl\Spanner\TimestampBound\MinReadTimestamp;
use Colopl\Spanner\TimestampBound\ReadTimestamp;
use Colopl\Spanner\TimestampBound\StrongRead;
use Generator;
use Google\Auth\FetchAuthTokenInterface;
use Google\Cloud\Core\Exception\AbortedException;
use Google\Cloud\Core\Exception\NotFoundException;
use Google\Cloud\Spanner\Duration;
use Google\Cloud\Spanner\KeySet;
use Google\Cloud\Spanner\Session\CacheSessionPool;
use Google\Cloud\Spanner\SpannerClient;
Expand All @@ -38,7 +40,6 @@
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use LogicException;
use RuntimeException;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use function dirname;
Expand All @@ -60,7 +61,7 @@ public function testReconnect(): void
{
$conn = $this->getDefaultConnection();
$conn->reconnect();
$this->assertEquals([12345], $conn->selectOne('SELECT 12345'));
$this->assertSame([12345], $conn->selectOne('SELECT 12345'));
}

public function testQueryLog(): void
Expand All @@ -75,6 +76,107 @@ public function testQueryLog(): void
$this->assertCount(2, $conn->getQueryLog());
}

public function test_select(): void
{
$conn = $this->getDefaultConnection();
$values = $conn->select('SELECT 12345');
$this->assertCount(1, $values);
$this->assertSame(12345, $values[0][0]);
}

public function test_selectWithOptions(): void
{
$conn = $this->getDefaultConnection();
$conn->table(self::TABLE_NAME_USER)->insert(['userId' => $this->generateUuid(), 'name' => __FUNCTION__]);
$values = $conn->selectWithOptions('SELECT * FROM ' . self::TABLE_NAME_USER, [], ['exactStaleness' => new Duration(10)]);
$this->assertEmpty($values);
}

public function test_cursorWithOptions(): void
{
$conn = $this->getDefaultConnection();
$conn->table(self::TABLE_NAME_USER)->insert(['userId' => $this->generateUuid(), 'name' => __FUNCTION__]);
$cursor = $conn->cursorWithOptions('SELECT * FROM ' . self::TABLE_NAME_USER, [], ['exactStaleness' => new Duration(10)]);
$this->assertInstanceOf(Generator::class, $cursor);
$this->assertNull($cursor->current());
}

public function test_statement_with_select(): void
{
$executedCount = 0;
$this->app['events']->listen(QueryExecuted::class, function () use (&$executedCount) { $executedCount++; });

$conn = $this->getDefaultConnection();
$res = $conn->statement('SELECT ?', ['12345']);

$this->assertTrue($res);
$this->assertSame(1, $executedCount);
}

public function test_statement_with_dml(): void
{
$conn = $this->getDefaultConnection();
$userId = $this->generateUuid();
$executedCount = 0;
$this->app['events']->listen(QueryExecuted::class, function () use (&$executedCount) { $executedCount++; });

$res[] = $conn->statement('INSERT '.self::TABLE_NAME_USER.' (`userId`, `name`) VALUES (?,?)', [$userId, __FUNCTION__]);
$res[] = $conn->statement('UPDATE '.self::TABLE_NAME_USER.' SET `name`=? WHERE `userId`=?', [__FUNCTION__.'2', $userId]);
$res[] = $conn->statement('DELETE '.self::TABLE_NAME_USER.' WHERE `userId`=?', [$this->generateUuid()]);

$this->assertTrue($res[0]);
$this->assertTrue($res[1]);
$this->assertTrue($res[2]);
$this->assertSame(3, $executedCount);
}

public function test_unprepared_with_select(): void
{
$executedCount = 0;
$this->app['events']->listen(QueryExecuted::class, function () use (&$executedCount) { $executedCount++; });

$conn = $this->getDefaultConnection();
$res = $conn->unprepared('SELECT 12345');

$this->assertTrue($res);
$this->assertSame(1, $executedCount);
}

public function test_unprepared_with_dml(): void
{
$conn = $this->getDefaultConnection();
$userId = $this->generateUuid();
$executedCount = 0;
$this->app['events']->listen(QueryExecuted::class, function () use (&$executedCount) { $executedCount++; });

$res[] = $conn->unprepared('INSERT '.self::TABLE_NAME_USER.' (`userId`, `name`) VALUES (\''.$userId.'\',\''.__FUNCTION__.'\')');
$res[] = $conn->unprepared('UPDATE '.self::TABLE_NAME_USER.' SET `name`=\''.__FUNCTION__.'2'.'\' WHERE `userId`=\''.$userId.'\'');
$res[] = $conn->unprepared('DELETE '.self::TABLE_NAME_USER.' WHERE `userId`=\''.$userId.'\'');

$this->assertTrue($res[0]);
$this->assertTrue($res[1]);
$this->assertTrue($res[2]);
$this->assertSame(3, $executedCount);
}

public function test_pretend(): void
{
$executedCount = 0;
$this->app['events']->listen(QueryExecuted::class, function () use (&$executedCount) { $executedCount++; });

$resSelect = null;
$resInsert = null;
$conn = $this->getDefaultConnection();
$conn->pretend(function(Connection $conn) use (&$resSelect, &$resInsert) {
$resSelect = $conn->select('SELECT 12345');
$resInsert = $conn->table(self::TABLE_NAME_USER)->insert(['userId' => $this->generateUuid(), 'name' => __FUNCTION__]);
});

$this->assertSame([], $resSelect);
$this->assertTrue($resInsert);
$this->assertSame(2, $executedCount);
}

public function testInsertUsingMutationWithTransaction(): void
{
Event::fake();
Expand Down
Loading

0 comments on commit 3bcd862

Please sign in to comment.