|
22 | 22 | import google.api_core.retry |
23 | 23 | import freezegun |
24 | 24 |
|
| 25 | +from google.cloud.bigquery.client import Client |
| 26 | +from google.cloud.bigquery import _job_helpers |
| 27 | + |
25 | 28 | from .helpers import make_connection |
26 | 29 |
|
27 | 30 |
|
@@ -242,38 +245,63 @@ def test_raises_on_job_retry_on_result_with_non_retryable_jobs(client): |
242 | 245 | job.result(job_retry=google.api_core.retry.Retry()) |
243 | 246 |
|
244 | 247 |
|
245 | | -@mock.patch("time.sleep") |
246 | | -def test_retry_ddl_query_rate_limit_exceeded(sleep, client): |
247 | | - """ |
248 | | - Specific test for retrying DDL queries with "jobRateLimitExceeded" error |
249 | | - """ |
250 | | - |
251 | | - err = dict(reason="jobRateLimitExceeded") |
252 | | - responses = [ |
253 | | - dict(status=dict(state="DONE", errors=[err], errorResult=err)), |
254 | | - dict(status=dict(state="DONE")), # Retry succeeds on second attempt |
255 | | - dict(rows=[{"f": [{"v": "DDL operation successful"}]}], totalRows="1"), |
256 | | - ] |
257 | | - |
258 | | - def api_request(method, path, query_params=None, data=None, **kw): |
259 | | - response = responses.pop(0) |
260 | | - if data: |
261 | | - response["jobReference"] = data["jobReference"] |
262 | | - else: |
263 | | - response["jobReference"] = dict( |
264 | | - jobId=path.split("/")[-1], projectId="PROJECT" |
265 | | - ) |
266 | | - return response |
267 | | - |
268 | | - conn = client._connection = make_connection() |
269 | | - conn.api_request.side_effect = api_request |
270 | | - |
271 | | - job = client.query( |
272 | | - "ALTER TABLE my_table ADD COLUMN new_column STRING", |
273 | | - job_retry=google.api_core.retry.Retry(), |
| 248 | +def test_query_and_wait_retries_job(): |
| 249 | + freezegun.freeze_time(auto_tick_seconds=100) |
| 250 | + client = mock.create_autospec(Client) |
| 251 | + client._call_api.__name__ = "_call_api" |
| 252 | + client._call_api.__qualname__ = "Client._call_api" |
| 253 | + client._call_api.__annotations__ = {} |
| 254 | + client._call_api.__type_params__ = () |
| 255 | + client._call_api.side_effect = ( |
| 256 | + google.api_core.exceptions.BadRequest("jobRateLimitExceeded"), |
| 257 | + google.api_core.exceptions.InternalServerError("jobRateLimitExceeded"), |
| 258 | + google.api_core.exceptions.BadRequest("jobRateLimitExceeded"), |
| 259 | + { |
| 260 | + "jobReference": { |
| 261 | + "projectId": "response-project", |
| 262 | + "jobId": "abc", |
| 263 | + "location": "response-location", |
| 264 | + }, |
| 265 | + "jobComplete": True, |
| 266 | + "schema": { |
| 267 | + "fields": [ |
| 268 | + {"name": "full_name", "type": "STRING", "mode": "REQUIRED"}, |
| 269 | + {"name": "age", "type": "INT64", "mode": "NULLABLE"}, |
| 270 | + ], |
| 271 | + }, |
| 272 | + "rows": [ |
| 273 | + {"f": [{"v": "Whillma Phlyntstone"}, {"v": "27"}]}, |
| 274 | + {"f": [{"v": "Bhetty Rhubble"}, {"v": "28"}]}, |
| 275 | + {"f": [{"v": "Phred Phlyntstone"}, {"v": "32"}]}, |
| 276 | + {"f": [{"v": "Bharney Rhubble"}, {"v": "33"}]}, |
| 277 | + ], |
| 278 | + }, |
274 | 279 | ) |
275 | | - result = job.result() |
276 | | - |
277 | | - assert result.total_rows == 1 |
278 | | - assert not responses # All calls made |
279 | | - assert len(sleep.mock_calls) == 1 # One retry attempt |
| 280 | + rows = _job_helpers.query_and_wait( |
| 281 | + client, |
| 282 | + query="SELECT 1", |
| 283 | + location="request-location", |
| 284 | + project="request-project", |
| 285 | + job_config=None, |
| 286 | + page_size=None, |
| 287 | + max_results=None, |
| 288 | + retry=google.api_core.retry.Retry( |
| 289 | + lambda exc: isinstance(exc, google.api_core.exceptions.BadRequest), |
| 290 | + multiplier=1.0, |
| 291 | + ).with_deadline( |
| 292 | + 200.0 |
| 293 | + ), # Since auto_tick_seconds is 100, we should get at least 1 retry. |
| 294 | + job_retry=google.api_core.retry.Retry( |
| 295 | + lambda exc: isinstance(exc, google.api_core.exceptions.InternalServerError), |
| 296 | + multiplier=1.0, |
| 297 | + ).with_deadline(600.0), |
| 298 | + ) |
| 299 | + assert len(list(rows)) == 4 |
| 300 | + |
| 301 | + # For this code path, where the query has finished immediately, we should |
| 302 | + # only be calling the jobs.query API and no other request path. |
| 303 | + request_path = "/projects/request-project/queries" |
| 304 | + for call in client._call_api.call_args_list: |
| 305 | + _, kwargs = call |
| 306 | + assert kwargs["method"] == "POST" |
| 307 | + assert kwargs["path"] == request_path |
0 commit comments