86
86
from google .cloud .bigquery .model import ModelReference
87
87
from google .cloud .bigquery .model import _model_arg_to_model_ref
88
88
from google .cloud .bigquery .query import _QueryResults
89
- from google .cloud .bigquery .retry import DEFAULT_RETRY
89
+ from google .cloud .bigquery .retry import DEFAULT_RETRY , DEFAULT_JOB_RETRY
90
90
from google .cloud .bigquery .routine import Routine
91
91
from google .cloud .bigquery .routine import RoutineReference
92
92
from google .cloud .bigquery .schema import SchemaField
@@ -3163,6 +3163,7 @@ def query(
3163
3163
project : str = None ,
3164
3164
retry : retries .Retry = DEFAULT_RETRY ,
3165
3165
timeout : float = None ,
3166
+ job_retry : retries .Retry = DEFAULT_JOB_RETRY ,
3166
3167
) -> job .QueryJob :
3167
3168
"""Run a SQL query.
3168
3169
@@ -3192,30 +3193,59 @@ def query(
3192
3193
Project ID of the project of where to run the job. Defaults
3193
3194
to the client's project.
3194
3195
retry (Optional[google.api_core.retry.Retry]):
3195
- How to retry the RPC.
3196
+ How to retry the RPC. This only applies to making RPC
3197
+ calls. It isn't used to retry failed jobs. This has
3198
+ a reasonable default that should only be overridden
3199
+ with care.
3196
3200
timeout (Optional[float]):
3197
3201
The number of seconds to wait for the underlying HTTP transport
3198
3202
before using ``retry``.
3203
+ job_retry (Optional[google.api_core.retry.Retry]):
3204
+ How to retry failed jobs. The default retries
3205
+ rate-limit-exceeded errors. Passing ``None`` disables
3206
+ job retry.
3207
+
3208
+ Not all jobs can be retried. If ``job_id`` is
3209
+ provided, then the job returned by the query will not
3210
+ be retryable, and an exception will be raised if a
3211
+ non-``None`` (and non-default) value for ``job_retry``
3212
+ is also provided.
3213
+
3214
+ Note that errors aren't detected until ``result()`` is
3215
+ called on the job returned. The ``job_retry``
3216
+ specified here becomes the default ``job_retry`` for
3217
+ ``result()``, where it can also be specified.
3199
3218
3200
3219
Returns:
3201
3220
google.cloud.bigquery.job.QueryJob: A new query job instance.
3202
3221
3203
3222
Raises:
3204
3223
TypeError:
3205
- If ``job_config`` is not an instance of :class:`~google.cloud.bigquery.job.QueryJobConfig`
3206
- class.
3224
+ If ``job_config`` is not an instance of
3225
+ :class:`~google.cloud.bigquery.job.QueryJobConfig`
3226
+ class, or if both ``job_id`` and non-``None`` non-default
3227
+ ``job_retry`` are provided.
3207
3228
"""
3208
3229
job_id_given = job_id is not None
3209
- job_id = _make_job_id (job_id , job_id_prefix )
3230
+ if (
3231
+ job_id_given
3232
+ and job_retry is not None
3233
+ and job_retry is not DEFAULT_JOB_RETRY
3234
+ ):
3235
+ raise TypeError (
3236
+ "`job_retry` was provided, but the returned job is"
3237
+ " not retryable, because a custom `job_id` was"
3238
+ " provided."
3239
+ )
3240
+
3241
+ job_id_save = job_id
3210
3242
3211
3243
if project is None :
3212
3244
project = self .project
3213
3245
3214
3246
if location is None :
3215
3247
location = self .location
3216
3248
3217
- job_config = copy .deepcopy (job_config )
3218
-
3219
3249
if self ._default_query_job_config :
3220
3250
if job_config :
3221
3251
_verify_job_config_type (
@@ -3225,6 +3255,8 @@ def query(
3225
3255
# that is in the default,
3226
3256
# should be filled in with the default
3227
3257
# the incoming therefore has precedence
3258
+ #
3259
+ # Note that _fill_from_default doesn't mutate the receiver
3228
3260
job_config = job_config ._fill_from_default (
3229
3261
self ._default_query_job_config
3230
3262
)
@@ -3233,34 +3265,54 @@ def query(
3233
3265
self ._default_query_job_config ,
3234
3266
google .cloud .bigquery .job .QueryJobConfig ,
3235
3267
)
3236
- job_config = copy . deepcopy ( self ._default_query_job_config )
3268
+ job_config = self ._default_query_job_config
3237
3269
3238
- job_ref = job ._JobReference (job_id , project = project , location = location )
3239
- query_job = job .QueryJob (job_ref , query , client = self , job_config = job_config )
3270
+ # Note that we haven't modified the original job_config (or
3271
+ # _default_query_job_config) up to this point.
3272
+ job_config_save = job_config
3240
3273
3241
- try :
3242
- query_job . _begin ( retry = retry , timeout = timeout )
3243
- except core_exceptions . Conflict as create_exc :
3244
- # The thought is if someone is providing their own job IDs and they get
3245
- # their job ID generation wrong, this could end up returning results for
3246
- # the wrong query. We thus only try to recover if job ID was not given.
3247
- if job_id_given :
3248
- raise create_exc
3274
+ def do_query () :
3275
+ # Make a copy now, so that original doesn't get changed by the process
3276
+ # below and to facilitate retry
3277
+ job_config = copy . deepcopy ( job_config_save )
3278
+
3279
+ job_id = _make_job_id ( job_id_save , job_id_prefix )
3280
+ job_ref = job . _JobReference ( job_id , project = project , location = location )
3281
+ query_job = job . QueryJob ( job_ref , query , client = self , job_config = job_config )
3249
3282
3250
3283
try :
3251
- query_job = self .get_job (
3252
- job_id ,
3253
- project = project ,
3254
- location = location ,
3255
- retry = retry ,
3256
- timeout = timeout ,
3257
- )
3258
- except core_exceptions .GoogleAPIError : # (includes RetryError)
3259
- raise create_exc
3284
+ query_job ._begin (retry = retry , timeout = timeout )
3285
+ except core_exceptions .Conflict as create_exc :
3286
+ # The thought is if someone is providing their own job IDs and they get
3287
+ # their job ID generation wrong, this could end up returning results for
3288
+ # the wrong query. We thus only try to recover if job ID was not given.
3289
+ if job_id_given :
3290
+ raise create_exc
3291
+
3292
+ try :
3293
+ query_job = self .get_job (
3294
+ job_id ,
3295
+ project = project ,
3296
+ location = location ,
3297
+ retry = retry ,
3298
+ timeout = timeout ,
3299
+ )
3300
+ except core_exceptions .GoogleAPIError : # (includes RetryError)
3301
+ raise create_exc
3302
+ else :
3303
+ return query_job
3260
3304
else :
3261
3305
return query_job
3262
- else :
3263
- return query_job
3306
+
3307
+ future = do_query ()
3308
+ # The future might be in a failed state now, but if it's
3309
+ # unrecoverable, we'll find out when we ask for it's result, at which
3310
+ # point, we may retry.
3311
+ if not job_id_given :
3312
+ future ._retry_do_query = do_query # in case we have to retry later
3313
+ future ._job_retry = job_retry
3314
+
3315
+ return future
3264
3316
3265
3317
def insert_rows (
3266
3318
self ,
0 commit comments