From d97c721d6f4c83bf230d216cac4687b924312197 Mon Sep 17 00:00:00 2001 From: Vishwaraj Anand Date: Tue, 20 Jun 2023 09:55:00 +0000 Subject: [PATCH] feat(Spanner): enable drop db protection (#6008) --- .../src/Connection/ConnectionInterface.php | 7 +++ Spanner/src/Connection/Grpc.php | 17 +++++++ Spanner/src/Database.php | 37 +++++++++++++++ Spanner/tests/System/AdminTest.php | 46 +++++++++++++++++++ Spanner/tests/Unit/DatabaseTest.php | 21 +++++++++ 5 files changed, 128 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..004185c78daa 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,21 @@ public function createDatabase(array $args) return $this->operationToArray($res, $this->serializer, $this->lroResponseMappers); } + /** + * @param array $args + */ + public function updateDatabase(array $args) + { + $databaseInfo = $this->serializer->decodeMessage(new Database(), $this->pluck('database', $args)); + $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..8e2022c1b331 100644 --- a/Spanner/src/Database.php +++ b/Spanner/src/Database.php @@ -450,6 +450,43 @@ 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' => $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..9409f94916c9 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,51 @@ public function testDatabase() $this->assertEquals($db->ddl()[0], $stmt); } + public function testDatabaseDropProtection() + { + $this->skipEmulatorTests(); + $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 */