From 1d5adeb81a2f4f85695ad6e03435e24e411518b5 Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Fri, 18 Aug 2023 18:15:01 +0900 Subject: [PATCH] [6.0] feat: implement data boost (#131) --- CHANGELOG.md | 1 + README.md | 22 ++++++++++++++ phpstan.neon | 3 ++ src/Concerns/ManagesSessionPool.php | 2 -- src/Concerns/ManagesStaleReads.php | 2 +- src/Connection.php | 22 ++++++++++++++ src/Query/Builder.php | 11 +++++-- src/Query/Concerns/UsesDataBoost.php | 44 ++++++++++++++++++++++++++++ tests/Query/BuilderTest.php | 21 +++++++++++++ 9 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 src/Query/Concerns/UsesDataBoost.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f808d5b7..0b8bcd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # v6.0.0 [Not released Yet] Added +- Add [Data Boost](https://cloud.google.com/spanner/docs/databoost/databoost-overview) support (#131) - 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) diff --git a/README.md b/README.md index 51e727d1..e5fdbb90 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,28 @@ $queryBuilder Stale reads always runs as read-only transaction with `singleUse` option. So you can not run as read-write transaction. +### Data Boost + +Data boost creates snapshot and runs the query in parallel without affecting existing workloads. + +You can read more about it [here](https://cloud.google.com/spanner/docs/databoost/databoost-overview). + +Below are some examples of how to use it. + +```php +// Using Connection +$connection->selectWithOptions('SELECT ...', $bindings, ['dataBoostEnabled' => true]); + +// Using Query Builder +$queryBuilder + ->useDataBoost() + ->get(); +``` + +> [!NOTE] +> This creates a new session in the background which is not shared with the current session pool. +> This means, queries running with data boost will not be associated with transactions that may be taking place. + ### Data Types Some data types of Google Cloud Spanner does not have corresponding built-in type of PHP. You can use following classes by [Google Cloud PHP Client](https://github.com/googleapis/google-cloud-php) diff --git a/phpstan.neon b/phpstan.neon index 3c5f02fc..0bbf16b4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -21,6 +21,9 @@ parameters: path: src/Connection.php - message: '#^Method Colopl\\Spanner\\Connection::selectWithOptions\(\) should return array but returns mixed\.$#' path: src/Connection.php + - message: '#^Generator expects value type array, mixed given.$#' + path: src/Connection.php + count: 1 - message: '#^Cannot cast mixed to int\.$#' path: src/Eloquent/Model.php - message: '#^Property Illuminate\\Database\\Schema\\Builder::\$resolver \(Closure\) in isset\(\) is not nullable\.$#' diff --git a/src/Concerns/ManagesSessionPool.php b/src/Concerns/ManagesSessionPool.php index 7101370a..8fc25e20 100644 --- a/src/Concerns/ManagesSessionPool.php +++ b/src/Concerns/ManagesSessionPool.php @@ -61,8 +61,6 @@ public function maintainSessionPool(): bool return false; } - - /** * @return int Number of warmed up sessions */ diff --git a/src/Concerns/ManagesStaleReads.php b/src/Concerns/ManagesStaleReads.php index 2f613ddb..0ac0d076 100644 --- a/src/Concerns/ManagesStaleReads.php +++ b/src/Concerns/ManagesStaleReads.php @@ -26,7 +26,7 @@ trait ManagesStaleReads { /** - * @deprecated use selectWithOptions() instead. This method will be removed in v7. + * @deprecated use cursorWithOptions() instead. This method will be removed in v7. * @param string $query * @param array $bindings * @param TimestampBoundInterface|null $timestampBound diff --git a/src/Connection.php b/src/Connection.php index a3230ea3..3a4f5772 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -591,11 +591,33 @@ protected function executeQuery(string $query, array $bindings, array $options): { $options += ['parameters' => $this->prepareBindings($bindings)]; + if (isset($options['dataBoostEnabled'])) { + return $this->executePartitionedQuery($query, $options); + } + return $this->getDatabaseContext() ->execute($query, $options) ->rows(); } + /** + * @param string $query + * @param array $options + * @return Generator> + */ + protected function executePartitionedQuery(string $query, array $options): Generator + { + $snapshot = $this->getSpannerClient() + ->batch($this->instanceId, $this->database, $options) + ->snapshot(); + + foreach ($snapshot->partitionQuery($query, $options) as $partition) { + foreach ($snapshot->executePartition($partition) as $row) { + yield $row; + } + } + } + /** * Check if this is "session not found" error * diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 47609f28..b7488832 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -24,9 +24,10 @@ class Builder extends BaseBuilder { - use Concerns\UsesMutations, - Concerns\UsesPartitionedDml, - Concerns\UsesStaleReads; + use Concerns\UsesDataBoost; + use Concerns\UsesMutations; + use Concerns\UsesPartitionedDml; + use Concerns\UsesStaleReads; /** * @var Connection @@ -136,6 +137,10 @@ protected function runSelect() $bindings = $this->getBindings(); $options = []; + if ($this->dataBoostEnabled()) { + $options += ['dataBoostEnabled' => true]; + } + if ($this->timestampBound !== null) { $options += $this->timestampBound->transactionOptions(); } diff --git a/src/Query/Concerns/UsesDataBoost.php b/src/Query/Concerns/UsesDataBoost.php new file mode 100644 index 00000000..c5fbcad5 --- /dev/null +++ b/src/Query/Concerns/UsesDataBoost.php @@ -0,0 +1,44 @@ +useDataBoost = $toggle; + return $this; + } + + /** + * @return bool + */ + public function dataBoostEnabled(): bool + { + return $this->useDataBoost; + } +} diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index 25887b5e..923deb85 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -836,4 +836,25 @@ public function test_toRawSql(): void $sql = $conn->table($table)->where('s', "t\"e\"s\nt")->toRawSql(); $this->assertSame("select * from `RawSqlTest` where `s` = r\"\"\"t\\\"e\\\"s\nt\"\"\"", $sql, 'newline with escaped quote'); } + + public function test_dataBoost_enabled(): void + { + $conn = $this->getDefaultConnection(); + $tableName = self::TABLE_NAME_USER; + + $conn->table($tableName)->insert(['userId' => $this->generateUuid(), 'name' => __FUNCTION__]); + + $query = $conn->table($tableName)->useDataBoost(); + $result = $query->get(); + + $this->assertTrue($query->dataBoostEnabled()); + $this->assertSame(1, $result->count()); + $this->assertSame(__FUNCTION__, $result->first()['name']); + } + + public function test_dataBoost_disabled(): void + { + $query = $this->getDefaultConnection()->table('t')->useDataBoost(false); + $this->assertFalse($query->dataBoostEnabled()); + } }