Repository: googleapis/python-bigquery

Issue:
Test TestBigQuery.test_transaction_info is flaky.
https://source.cloud.google.com/results/invocations/2ca6ab31-ba0f-4a6b-ab53-55943a5bac42/targets/cloud-devrel%2Fclient-libraries%2Fpython%2Fgoogleapis%2Fpython-bigquery%2Fpresubmit%2Fsystem-3.8/log

This test fails pretty regularly for me.


Discussion:
I checked the job state for a recent failure:

```json
{
  "kind": "bigquery#job",
  "etag": "NSFsBEFD1+My1joEvfuzGQ==",
  "id": "precise-truck-742:US.a9a56a2e-836e-4679-add5-941543bfbf8f",
  "selfLink": "https://content-bigquery.googleapis.com/bigquery/v2/projects/precise-truck-742/jobs/a9a56a2e-836e-4679-add5-941543bfbf8f?location=US",
  "user_email": "REDACTED",
  "configuration": {
    "query": {
      "query": "\n            BEGIN TRANSACTION;\n              INSERT INTO `precise-truck-742.python_bigquery_tests_system_bq_system_test_1632082968448.test_dml_statistics`\n              VALUES (\"one\", 1), (\"two\", 2), (\"three\", 3), (\"four\", 4);\n\n              UPDATE `precise-truck-742.python_bigquery_tests_system_bq_system_test_1632082968448.test_dml_statistics`\n              SET bar = bar + 1\n              WHERE bar \u003e 2;\n            COMMIT TRANSACTION;\n        ",
      "priority": "INTERACTIVE",
      "useLegacySql": false
    },
    "jobType": "QUERY"
  },
  "jobReference": {
    "projectId": "precise-truck-742",
    "jobId": "a9a56a2e-836e-4679-add5-941543bfbf8f",
    "location": "US"
  },
  "statistics": {
    "creationTime": "1632082969228",
    "startTime": "1632082969269",
    "endTime": "1632082974219",
    "totalBytesProcessed": "55",
    "query": {
      "totalBytesProcessed": "55",
      "totalBytesBilled": "10485760",
      "totalSlotMs": "19050",
      "statementType": "SCRIPT"
    },
    "totalSlotMs": "19050",
    "numChildJobs": "4"
  },
  "status": {
    "state": "DONE"
  }
}
```

Looks like no `transactionInfo` key was ever added. I've filed internal issue 200542305 to have the backend team investigate further.
According to the backend folks, since a query job can contain multiple transactions, this key is not intended to be present on the parent job. Instead, we need to iterate through the child jobs to find the one that began the transaction.

They are investigating why `transactionInfo` appears on the parent job at all (more often than not).

Resolving patch:
diff --git a/google/cloud/bigquery/job/base.py b/google/cloud/bigquery/job/base.py
--- a/google/cloud/bigquery/job/base.py
+++ b/google/cloud/bigquery/job/base.py
@@ -357,6 +357,11 @@ def reservation_usage(self):
     def transaction_info(self) -> Optional[TransactionInfo]:
         """Information of the multi-statement transaction if this job is part of one.
 
+        Since a scripting query job can execute multiple transactions, this
+        property is only expected on child jobs. Use the
+        :meth:`google.cloud.bigquery.client.Client.list_jobs` method with the
+        ``parent_job`` parameter to iterate over child jobs.
+
         .. versionadded:: 2.24.0
         """
         info = self._properties.get("statistics", {}).get("transactionInfo")


Test patch:
diff --git a/tests/system/test_client.py b/tests/system/test_client.py
--- a/tests/system/test_client.py
+++ b/tests/system/test_client.py
@@ -1586,9 +1586,15 @@ def test_transaction_info(self):
         query_job = Config.CLIENT.query(sql)
         query_job.result()
 
-        # Transaction ID set by the server should be accessible
-        assert query_job.transaction_info is not None
-        assert query_job.transaction_info.transaction_id != ""
+        child_jobs = Config.CLIENT.list_jobs(parent_job=query_job)
+        begin_transaction_job = next(iter(child_jobs))
+
+        # Transaction ID set by the server should be accessible on the child
+        # job responsible for `BEGIN TRANSACTION`. It is not expected to be
+        # present on the parent job itself.
+        # https://github.com/googleapis/python-bigquery/issues/975
+        assert begin_transaction_job.transaction_info is not None
+        assert begin_transaction_job.transaction_info.transaction_id != ""
 
     def test_dbapi_w_standard_sql_types(self):
         for sql, expected in helpers.STANDARD_SQL_EXAMPLES: