Skip to content

Commit faedc4a

Browse files
feat: Add snapshot methods to query Builder/Model (#267)
1 parent 6bc55bd commit faedc4a

File tree

5 files changed

+122
-0
lines changed

5 files changed

+122
-0
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,32 @@ $queryBuilder
183183

184184
Stale reads always runs as read-only transaction with `singleUse` option. So you can not run as read-write transaction.
185185

186+
### Snapshot reads
187+
188+
You can use explicit Snapshot reads, either on `Connection`, or on `Model` or `Builder` instances. When running `snapshot()` on `Connection`, you pass a `Closure` that you can use to run multiple reads from within the same Snapshot.
189+
190+
```php
191+
$timestampBound = new ExactStaleness(10);
192+
193+
// by Connection
194+
$connection->snapshot($timestampBound, function() use ($connection) {
195+
$result1 = $connection->table('foo')->get();
196+
$result2 = $connection->table('bar')->get();
197+
198+
return [$result1, $result2];
199+
);
200+
201+
// by Model
202+
User::where('foo', 'bar')
203+
->snapshot($timestampBound)
204+
->get();
205+
206+
// by Query Builder
207+
$queryBuilder
208+
->snapshot($timestampBound)
209+
->get();
210+
```
211+
186212
### Data Boost
187213

188214
Data boost creates snapshot and runs the query in parallel without affecting existing workloads.

src/Connection.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Colopl\Spanner\Query\Processor as QueryProcessor;
2626
use Colopl\Spanner\Schema\Builder as SchemaBuilder;
2727
use Colopl\Spanner\Schema\Grammar as SchemaGrammar;
28+
use Colopl\Spanner\TimestampBound\TimestampBoundInterface;
2829
use DateTimeImmutable;
2930
use DateTimeInterface;
3031
use Exception;
@@ -625,6 +626,12 @@ protected function executeQuery(string $query, array $bindings, array $options):
625626
$options['requestOptions']['requestTag'] = $tag;
626627
}
627628

629+
if (isset($options['snapshotEnabled'])) {
630+
$timestamp = $options['snapshotTimestampBound'];
631+
assert($timestamp instanceof TimestampBoundInterface);
632+
return $this->snapshot($timestamp, fn() => $this->executeSnapshotQuery($query, $options));
633+
}
634+
628635
if ($this->inSnapshot()) {
629636
return $this->executeSnapshotQuery($query, $options);
630637
}

src/Query/Builder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Builder extends BaseBuilder
3232
use Concerns\UsesFullTextSearch;
3333
use Concerns\UsesMutations;
3434
use Concerns\UsesPartitionedDml;
35+
use Concerns\UsesSnapshots;
3536
use Concerns\UsesStaleReads;
3637

3738
public const PARAMETER_LIMIT = 950;
@@ -242,6 +243,11 @@ protected function runSelect()
242243
$options['dataBoostEnabled'] = true;
243244
}
244245

246+
if ($this->snapshotEnabled()) {
247+
$options['snapshotEnabled'] = $this->snapshotEnabled();
248+
$options['snapshotTimestampBound'] = $this->snapshotTimestampBound();
249+
}
250+
245251
if ($this->timestampBound !== null) {
246252
$options += $this->timestampBound->transactionOptions();
247253
}

src/Query/Concerns/UsesSnapshots.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Colopl Inc. All Rights Reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace Colopl\Spanner\Query\Concerns;
19+
20+
use Colopl\Spanner\TimestampBound\TimestampBoundInterface;
21+
22+
trait UsesSnapshots
23+
{
24+
/**
25+
* @var TimestampBoundInterface
26+
*/
27+
protected ?TimestampBoundInterface $snapshotTimestampBound;
28+
29+
/**
30+
* @param TimestampBoundInterface $timestamp
31+
* @return $this
32+
*/
33+
public function snapshot(TimestampBoundInterface $timestamp): static
34+
{
35+
$this->snapshotTimestampBound = $timestamp;
36+
return $this;
37+
}
38+
39+
/**
40+
* @return bool
41+
*/
42+
public function snapshotEnabled(): bool
43+
{
44+
return isset($this->snapshotTimestampBound);
45+
}
46+
47+
/**
48+
* @return TimestampBoundInterface|null
49+
*/
50+
public function snapshotTimestampBound(): TimestampBoundInterface|null
51+
{
52+
return $this->snapshotTimestampBound;
53+
}
54+
}

tests/Query/BuilderTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Colopl\Spanner\Schema\TokenizerFunction;
2525
use Colopl\Spanner\Tests\TestCase;
2626
use Colopl\Spanner\TimestampBound\ExactStaleness;
27+
use Colopl\Spanner\TimestampBound\StrongRead;
2728
use Google\Cloud\Spanner\Bytes;
2829
use Google\Cloud\Spanner\Duration;
2930
use Illuminate\Database\QueryException;
@@ -1074,6 +1075,34 @@ public function test_dataBoost_disabled(): void
10741075
$this->assertFalse($query->dataBoostEnabled());
10751076
}
10761077

1078+
public function test_snapshot(): void
1079+
{
1080+
$conn = $this->getDefaultConnection();
1081+
1082+
$conn->transaction(function () use ($conn) {
1083+
$this->assertFalse($conn->inSnapshot());
1084+
$conn->table(self::TABLE_NAME_USER)->insert(['userId' => $this->generateUuid(), 'name' => 't']);
1085+
});
1086+
1087+
$this->assertFalse($conn->inSnapshot());
1088+
$query = $conn->table(self::TABLE_NAME_USER)->snapshot(new ExactStaleness(5));
1089+
$result = $query->first();
1090+
1091+
$this->assertTrue($query->snapshotEnabled());
1092+
$this->assertInstanceOf(ExactStaleness::class, $query->snapshotTimestampBound());
1093+
$this->assertFalse($conn->inSnapshot());
1094+
$this->assertNull($result);
1095+
1096+
$query = $conn->table(self::TABLE_NAME_USER)->snapshot(new StrongRead());
1097+
$result = $query->first();
1098+
1099+
$this->assertTrue($query->snapshotEnabled());
1100+
$this->assertInstanceOf(StrongRead::class, $query->snapshotTimestampBound());
1101+
$this->assertFalse($conn->inSnapshot());
1102+
$this->assertNotNull($result);
1103+
$this->assertSame('t', $result['name']);
1104+
}
1105+
10771106
public function test_setRequestTimeoutSeconds(): void
10781107
{
10791108
$query = $this->getDefaultConnection()->table(self::TABLE_NAME_USER);

0 commit comments

Comments
 (0)