Repository: googleapis/google-cloud-python

Issue:
Spanner: Operation_id missing in Spanner's Database.update_ddl method
Library: google_cloud_spanner v. 1.6.1

We seem to miss operation_id in spanner_v1 of database.update_ddl(). 

It looks like the Spanner HTTP API supports operation_id: https://cloud.google.com/spanner/docs/reference/rest/v1/projects.instances.databases/updateDdl
GoogleAPI docs mention that Spanner's database.update_ddl() supports operation_id:
https://googleapis.github.io/google-cloud-python/latest/spanner/database-usage.html#update-an-existing-database.

Wy use (from pypi) google_cloud_spanner v. 1.6.1: This seems to be the latest released version (Released 10 Nov 2018). In the code of the library's Database object - contrary to the documentation there is no operation_id parameter in update_ddl statement and the operation_id seems to be hard-coded to “” in the actual API call:

https://github.com/googleapis/google-cloud-python/blob/master/spanner/google/cloud/spanner_v1/database.py#L257 

Is it intended? How else can we use operation_id


Discussion:
Having that parameter missing from `Database.update_ddl()` looks like an oversight to me.

Resolving patch:
diff --git a/spanner/google/cloud/spanner_v1/database.py b/spanner/google/cloud/spanner_v1/database.py
--- a/spanner/google/cloud/spanner_v1/database.py
+++ b/spanner/google/cloud/spanner_v1/database.py
@@ -254,7 +254,7 @@ def reload(self):
         response = api.get_database_ddl(self.name, metadata=metadata)
         self._ddl_statements = tuple(response.statements)
 
-    def update_ddl(self, ddl_statements):
+    def update_ddl(self, ddl_statements, operation_id=''):
         """Update DDL for this database.
 
         Apply any configured schema from :attr:`ddl_statements`.
@@ -264,6 +264,8 @@ def update_ddl(self, ddl_statements):
 
         :type ddl_statements: Sequence[str]
         :param ddl_statements: a list of DDL statements to use on this database
+        :type operation_id: str
+        :param operation_id: (optional) a string ID for the long-running operation
 
         :rtype: :class:`google.api_core.operation.Operation`
         :returns: an operation instance
@@ -274,7 +276,7 @@ def update_ddl(self, ddl_statements):
         metadata = _metadata_with_prefix(self.name)
 
         future = api.update_database_ddl(
-            self.name, ddl_statements, "", metadata=metadata
+            self.name, ddl_statements, operation_id=operation_id, metadata=metadata
         )
         return future
 


Test patch:
diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py
--- a/spanner/tests/system/test_system.py
+++ b/spanner/tests/system/test_system.py
@@ -21,6 +21,7 @@
 import threading
 import time
 import unittest
+import uuid
 
 import pytest
 
@@ -323,7 +324,7 @@ def test_table_not_found(self):
             "5629"
         )
     )
-    def test_update_database_ddl(self):
+    def test_update_database_ddl_with_operation_id(self):
         pool = BurstyPool(labels={"testcase": "update_database_ddl"})
         temp_db_id = "temp_db" + unique_resource_id("_")
         temp_db = Config.INSTANCE.database(temp_db_id, pool=pool)
@@ -331,12 +332,15 @@ def test_update_database_ddl(self):
         self.to_delete.append(temp_db)
 
         # We want to make sure the operation completes.
-        create_op.result(120)  # raises on failure / timeout.
+        create_op.result(240)  # raises on failure / timeout.
+        # random but shortish always start with letter
+        operation_id = 'a' + str(uuid.uuid4())[:8]
+        operation = temp_db.update_ddl(DDL_STATEMENTS, operation_id=operation_id)
 
-        operation = temp_db.update_ddl(DDL_STATEMENTS)
+        self.assertEqual(operation_id, operation.operation.name.split('/')[-1])
 
         # We want to make sure the operation completes.
-        operation.result(120)  # raises on failure / timeout.
+        operation.result(240)  # raises on failure / timeout.
 
         temp_db.reload()
 
diff --git a/spanner/tests/unit/test_database.py b/spanner/tests/unit/test_database.py
--- a/spanner/tests/unit/test_database.py
+++ b/spanner/tests/unit/test_database.py
@@ -588,6 +588,28 @@ def test_update_ddl(self):
             metadata=[("google-cloud-resource-prefix", database.name)],
         )
 
+    def test_update_ddl_w_operation_id(self):
+        from tests._fixtures import DDL_STATEMENTS
+
+        op_future = object()
+        client = _Client()
+        api = client.database_admin_api = self._make_database_admin_api()
+        api.update_database_ddl.return_value = op_future
+        instance = _Instance(self.INSTANCE_NAME, client=client)
+        pool = _Pool()
+        database = self._make_one(self.DATABASE_ID, instance, pool=pool)
+
+        future = database.update_ddl(DDL_STATEMENTS, operation_id='someOperationId')
+
+        self.assertIs(future, op_future)
+
+        api.update_database_ddl.assert_called_once_with(
+            self.DATABASE_NAME,
+            DDL_STATEMENTS,
+            "someOperationId",
+            metadata=[("google-cloud-resource-prefix", database.name)],
+        )
+
     def test_drop_grpc_error(self):
         from google.api_core.exceptions import Unknown