From 3be24cf51d0ecb76c221d873e468f821135b6bea Mon Sep 17 00:00:00 2001 From: Vishwaraj Anand Date: Sat, 17 Jun 2023 15:06:56 +0530 Subject: [PATCH] feat(Spanner): enable drop db protection --- .../src/Connection/ConnectionInterface.php | 7 +++ Spanner/src/Connection/Grpc.php | 18 ++++++++ Spanner/src/Database.php | 39 ++++++++++++++++ Spanner/tests/System/AdminTest.php | 45 +++++++++++++++++++ Spanner/tests/Unit/DatabaseTest.php | 21 +++++++++ 5 files changed, 130 insertions(+) diff --git a/Spanner/src/Connection/ConnectionInterface.php b/Spanner/src/Connection/ConnectionInterface.php index 52a87f1a35af..ebf70141b424 100644 --- a/Spanner/src/Connection/ConnectionInterface.php +++ b/Spanner/src/Connection/ConnectionInterface.php @@ -21,6 +21,8 @@ /** * Describes a connection to the Cloud Spanner API + * + * @internal */ interface ConnectionInterface { @@ -148,6 +150,11 @@ public function listDatabases(array $args); */ public function createDatabase(array $args); + /** + * @param array $args + */ + public function updateDatabase(array $args); + /** * @param array $args */ diff --git a/Spanner/src/Connection/Grpc.php b/Spanner/src/Connection/Grpc.php index 88abdfc57c27..1a7a04c515bf 100644 --- a/Spanner/src/Connection/Grpc.php +++ b/Spanner/src/Connection/Grpc.php @@ -74,6 +74,8 @@ /** * Connection to Cloud Spanner over gRPC + * + * @internal */ class Grpc implements ConnectionInterface { @@ -679,6 +681,22 @@ public function createDatabase(array $args) return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); } + /** + * @param array $args + */ + public function updateDatabase(array $args) + { + $database = $this->pluck('database', $args); + $databaseInfo = $this->serializer->decodeMessage(new Database(), $database); + $databaseName = $databaseInfo->getName(); + $updateMask = $this->serializer->decodeMessage(new FieldMask(), $this->pluck('updateMask', $args)); + return $this->send([$this->getDatabaseAdminClient(), 'updateDatabase'], [ + $databaseInfo, + $updateMask, + $this->addResourcePrefixHeader($args, $databaseName) + ]); + } + /** * @param array $args */ diff --git a/Spanner/src/Database.php b/Spanner/src/Database.php index 7415e5278849..0ace4a1bbe90 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -450,6 +450,45 @@ public function restore($backup, array $options = []) return $this->instance->createDatabaseFromBackup($this->name, $backup, $options); } + /** + * Update an existing Cloud Spanner database. + * + * Example: + * ``` + * $operation = $database->updateDatabase(['enableDropProtection' => true]); + * ``` + * + * @codingStandardsIgnoreStart + * @see https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#updatedatabaserequest UpdateDatabaseRequest + * @codingStandardsIgnoreEnd + * + * @param array $options [optional] { + * Configuration Options + * + * @type bool $enableDropProtection If true, delete operations for Database + * and Instance will be blocked. **Defaults to** `false`. + * } + * @return LongRunningOperation + */ + public function updateDatabase(array $options = []) + { + $fieldMask = []; + if (isset($options['enableDropProtection'])) { + $fieldMask[] = 'enable_drop_protection'; + } + return $this->info = $this->connection->updateDatabase([ + 'database' => [ + 'name' => $this->name, + 'enableDropProtection' => isset($options['enableDropProtection']) + ? $options['enableDropProtection'] + : false, + ], + 'updateMask' => [ + 'paths' => $fieldMask + ] + ] + $options); + } + /** * Update the Database schema by running a SQL statement. * diff --git a/Spanner/tests/System/AdminTest.php b/Spanner/tests/System/AdminTest.php index 60ac9a735f05..f91a71c4e766 100644 --- a/Spanner/tests/System/AdminTest.php +++ b/Spanner/tests/System/AdminTest.php @@ -17,6 +17,7 @@ namespace Google\Cloud\Spanner\Tests\System; +use Google\Cloud\Core\Exception\FailedPreconditionException; use Google\Cloud\Core\LongRunning\LongRunningOperation; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseAdminClient; use Google\Cloud\Spanner\Admin\Database\V1\DatabaseDialect; @@ -129,6 +130,50 @@ public function testDatabase() $this->assertEquals($db->ddl()[0], $stmt); } + public function testDatabaseDropProtection() + { + $instance = self::$instance; + + $dbName = uniqid(self::TESTING_PREFIX); + $op = $instance->createDatabase($dbName); + + $this->assertInstanceOf(LongRunningOperation::class, $op); + $db = $op->pollUntilComplete(); + $this->assertInstanceOf(Database::class, $db); + + $info = $db->reload(); + $this->assertFalse($info['enableDropProtection']); + + $op = $db->updateDatabase(['enableDropProtection' => true]); + $op->pollUntilComplete(); + $info = $db->reload(); + $this->assertTrue($info['enableDropProtection']); + + // delete database should throw + $isDropThrows = false; + try { + $db->drop(); + } catch (FailedPreconditionException $ex) { + $isDropThrows = true; + $this->assertStringContainsStringIgnoringCase( + 'enable_drop_protection', + $ex->getMessage() + ); + } + $this->assertTrue($isDropThrows); + $this->assertTrue($db->exists()); + + // disable drop databases config + $op = $db->updateDatabase(['enableDropProtection' => false]); + $op->pollUntilComplete(); + $info = $db->reload(); + $this->assertFalse($info['enableDropProtection']); + + // drop should succeed + $db->drop(); + $this->assertFalse($db->exists()); + } + public function testCreateCustomerManagedInstanceConfiguration() { $this->skipEmulatorTests(); diff --git a/Spanner/tests/Unit/DatabaseTest.php b/Spanner/tests/Unit/DatabaseTest.php index c2f38bb4c0c2..6822bd141a60 100644 --- a/Spanner/tests/Unit/DatabaseTest.php +++ b/Spanner/tests/Unit/DatabaseTest.php @@ -332,6 +332,27 @@ public function testCreate() $this->assertInstanceOf(LongRunningOperation::class, $op); } + /** + * @group spanner-admin + */ + public function testUpdateDatabase() + { + $this->connection->updateDatabase(Argument::allOf( + Argument::withEntry('database', [ + 'name' => DatabaseAdminClient::databaseName(self::PROJECT, self::INSTANCE, self::DATABASE), + 'enableDropProtection' => true, + ]), + Argument::withEntry('updateMask', ['paths' => ['enable_drop_protection']]) + ))->shouldBeCalledTimes(1)->willReturn([ + 'enableDropProtection' => true + ]); + + $this->database->___setProperty('connection', $this->connection->reveal()); + + $res = $this->database->updateDatabase(['enableDropProtection' => true]); + $this->assertTrue($res['enableDropProtection']); + } + /** * @group spanner-admin */