Skip to content

Commit e4163d0

Browse files
committed
feat: drop database protection
1 parent 35953e5 commit e4163d0

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

google/cloud/spanner_v1/database.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from google.api_core import gapic_v1
3030
from google.iam.v1 import iam_policy_pb2
3131
from google.iam.v1 import options_pb2
32+
from google.protobuf.field_mask_pb2 import FieldMask
3233

3334
from google.cloud.spanner_admin_database_v1 import CreateDatabaseRequest
3435
from google.cloud.spanner_admin_database_v1 import Database as DatabasePB
@@ -124,6 +125,9 @@ class Database(object):
124125
(Optional) database dialect for the database
125126
:type database_role: str or None
126127
:param database_role: (Optional) user-assigned database_role for the session.
128+
:type drop_protection_enabled: boolean
129+
:param drop_protection_enabled: (Optional) Represents whether the database
130+
has drop protection enabled or not.
127131
"""
128132

129133
_spanner_api = None
@@ -138,6 +142,7 @@ def __init__(
138142
encryption_config=None,
139143
database_dialect=DatabaseDialect.DATABASE_DIALECT_UNSPECIFIED,
140144
database_role=None,
145+
drop_protection_enabled=False,
141146
):
142147
self.database_id = database_id
143148
self._instance = instance
@@ -155,6 +160,8 @@ def __init__(
155160
self._encryption_config = encryption_config
156161
self._database_dialect = database_dialect
157162
self._database_role = database_role
163+
self.drop_protection_enabled = drop_protection_enabled
164+
self._reconciling = False
158165

159166
if pool is None:
160167
pool = BurstyPool(database_role=database_role)
@@ -328,6 +335,15 @@ def database_role(self):
328335
"""
329336
return self._database_role
330337

338+
@property
339+
def reconciling(self):
340+
"""Whether the database is currently reconciling.
341+
342+
:rtype: boolean
343+
:returns: a boolean representing whether the database is reconciling
344+
"""
345+
return self._reconciling
346+
331347
@property
332348
def logger(self):
333349
"""Logger used by the database.
@@ -457,6 +473,7 @@ def reload(self):
457473
self._encryption_info = response.encryption_info
458474
self._default_leader = response.default_leader
459475
self._database_dialect = response.database_dialect
476+
self.drop_protection_enabled = response.enable_drop_protection
460477

461478
def update_ddl(self, ddl_statements, operation_id=""):
462479
"""Update DDL for this database.
@@ -488,6 +505,42 @@ def update_ddl(self, ddl_statements, operation_id=""):
488505
future = api.update_database_ddl(request=request, metadata=metadata)
489506
return future
490507

508+
def update(self):
509+
"""Update this database.
510+
511+
See
512+
TODO: insert documentation once ready
513+
514+
.. note::
515+
516+
Updates the `drop_protection_enabled` field. To change this value
517+
before updating, set it via
518+
519+
.. code:: python
520+
521+
database.drop_protection_enabled = True
522+
523+
before calling :meth:`update`.
524+
525+
:rtype: :class:`google.api_core.operation.Operation`
526+
:returns: an operation instance
527+
:raises NotFound: if the database does not exist
528+
"""
529+
api = self._instance._client.database_admin_api
530+
database_pb = DatabasePB(
531+
name=self.name, enable_drop_protection=self.drop_protection_enabled
532+
)
533+
534+
# Only support updating drop protection for now.
535+
field_mask = FieldMask(paths=["enable_drop_protection"])
536+
metadata = _metadata_with_prefix(self.name)
537+
538+
future = api.update_database(
539+
database=database_pb, update_mask=field_mask, metadata=metadata
540+
)
541+
542+
return future
543+
491544
def drop(self):
492545
"""Drop this database.
493546

tests/system/test_database_api.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,37 @@ def _unit_of_work(transaction, name):
562562
rows = list(after.read(sd.COUNTERS_TABLE, sd.COUNTERS_COLUMNS, sd.ALL))
563563

564564
assert len(rows) == 2
565+
566+
def test_update_database(
567+
not_emulator,
568+
shared_database,
569+
shared_instance,
570+
database_operation_timeout
571+
):
572+
old_protection = shared_database.drop_protection_enabled
573+
new_protection = True
574+
shared_database.drop_protection_enabled = new_protection
575+
operation = shared_database.update()
576+
577+
# We want to make sure the operation completes.
578+
operation.result(database_operation_timeout) # raises on failure / timeout.
579+
580+
# Create a new database instance and reload it.
581+
database_alt = shared_instance.database(shared_database.name.split("/")[-1])
582+
assert database_alt.drop_protection_enabled != new_protection
583+
584+
database_alt.reload()
585+
assert database_alt.drop_protection_enabled == new_protection
586+
587+
with pytest.raises(exceptions.FailedPrecondition,
588+
match='`enable_drop_protection` setting'):
589+
database_alt.drop()
590+
591+
with pytest.raises(exceptions.FailedPrecondition,
592+
match='drop protection enabled'):
593+
shared_instance.delete()
594+
595+
# Make sure to put the database back the way it was for the
596+
# other test cases.
597+
shared_database.drop_protection_enabled = old_protection
598+
shared_database.update()

tests/unit/test_database.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import mock
1919
from google.api_core import gapic_v1
20+
from google.cloud.spanner_admin_database_v1 import Database as DatabasePB
2021
from google.cloud.spanner_v1.param_types import INT64
2122
from google.api_core.retry import Retry
23+
from google.protobuf.field_mask_pb2 import FieldMask
2224

2325
from google.cloud.spanner_v1 import RequestOptions
2426

@@ -877,6 +879,33 @@ def test_update_ddl_w_operation_id(self):
877879
metadata=[("google-cloud-resource-prefix", database.name)],
878880
)
879881

882+
def test_update_success(self):
883+
op_future = object()
884+
client = _Client()
885+
api = client.database_admin_api = self._make_database_admin_api()
886+
api.update_database.return_value = op_future
887+
888+
instance = _Instance(self.INSTANCE_NAME, client=client)
889+
pool = _Pool()
890+
database = self._make_one(
891+
self.DATABASE_ID, instance, drop_protection_enabled=True, pool=pool
892+
)
893+
894+
future = database.update()
895+
896+
self.assertIs(future, op_future)
897+
898+
expected_database = DatabasePB(name=database.name,
899+
enable_drop_protection=True)
900+
901+
field_mask = FieldMask(paths=["enable_drop_protection"])
902+
903+
api.update_database.assert_called_once_with(
904+
database=expected_database,
905+
update_mask=field_mask,
906+
metadata=[("google-cloud-resource-prefix", database.name)],
907+
)
908+
880909
def test_drop_grpc_error(self):
881910
from google.api_core.exceptions import Unknown
882911

0 commit comments

Comments
 (0)