Skip to content

Commit 4ba4342

Browse files
authored
fix: query_and_wait now retains unknown query configuration _properties (#1793)
* fix: `query_and_wait` now retains unknown query configuration `_properties` fix: raise `ValueError` in `query_and_wait` with wrong `job_config` type
1 parent 89f1299 commit 4ba4342

File tree

2 files changed

+79
-20
lines changed

2 files changed

+79
-20
lines changed

google/cloud/bigquery/_job_helpers.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ def do_query():
166166
return future
167167

168168

169+
def _validate_job_config(request_body: Dict[str, Any], invalid_key: str):
170+
"""Catch common mistakes, such as passing in a *JobConfig object of the
171+
wrong type.
172+
"""
173+
if invalid_key in request_body:
174+
raise ValueError(f"got unexpected key {repr(invalid_key)} in job_config")
175+
176+
169177
def _to_query_request(
170178
job_config: Optional[job.QueryJobConfig] = None,
171179
*,
@@ -179,17 +187,15 @@ def _to_query_request(
179187
QueryRequest. If any configuration property is set that is not available in
180188
jobs.query, it will result in a server-side error.
181189
"""
182-
request_body = {}
183-
job_config_resource = job_config.to_api_repr() if job_config else {}
184-
query_config_resource = job_config_resource.get("query", {})
190+
request_body = copy.copy(job_config.to_api_repr()) if job_config else {}
185191

186-
request_body.update(query_config_resource)
192+
_validate_job_config(request_body, job.CopyJob._JOB_TYPE)
193+
_validate_job_config(request_body, job.ExtractJob._JOB_TYPE)
194+
_validate_job_config(request_body, job.LoadJob._JOB_TYPE)
187195

188-
# These keys are top level in job resource and query resource.
189-
if "labels" in job_config_resource:
190-
request_body["labels"] = job_config_resource["labels"]
191-
if "dryRun" in job_config_resource:
192-
request_body["dryRun"] = job_config_resource["dryRun"]
196+
# Move query.* properties to top-level.
197+
query_config_resource = request_body.pop("query", {})
198+
request_body.update(query_config_resource)
193199

194200
# Default to standard SQL.
195201
request_body.setdefault("useLegacySql", False)

tests/unit/test__job_helpers.py

+64-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
from google.cloud.bigquery.client import Client
2525
from google.cloud.bigquery import _job_helpers
26+
from google.cloud.bigquery.job import copy_ as job_copy
27+
from google.cloud.bigquery.job import extract as job_extract
28+
from google.cloud.bigquery.job import load as job_load
2629
from google.cloud.bigquery.job import query as job_query
2730
from google.cloud.bigquery.query import ConnectionProperty, ScalarQueryParameter
2831

@@ -57,9 +60,34 @@ def make_query_response(
5760
@pytest.mark.parametrize(
5861
("job_config", "expected"),
5962
(
60-
(None, make_query_request()),
61-
(job_query.QueryJobConfig(), make_query_request()),
62-
(
63+
pytest.param(
64+
None,
65+
make_query_request(),
66+
id="job_config=None-default-request",
67+
),
68+
pytest.param(
69+
job_query.QueryJobConfig(),
70+
make_query_request(),
71+
id="job_config=QueryJobConfig()-default-request",
72+
),
73+
pytest.param(
74+
job_query.QueryJobConfig.from_api_repr(
75+
{
76+
"unknownTopLevelProperty": "some-test-value",
77+
"query": {
78+
"unknownQueryProperty": "some-other-value",
79+
},
80+
},
81+
),
82+
make_query_request(
83+
{
84+
"unknownTopLevelProperty": "some-test-value",
85+
"unknownQueryProperty": "some-other-value",
86+
}
87+
),
88+
id="job_config-with-unknown-properties-includes-all-properties-in-request",
89+
),
90+
pytest.param(
6391
job_query.QueryJobConfig(default_dataset="my-project.my_dataset"),
6492
make_query_request(
6593
{
@@ -69,17 +97,24 @@ def make_query_response(
6997
}
7098
}
7199
),
100+
id="job_config-with-default_dataset",
72101
),
73-
(job_query.QueryJobConfig(dry_run=True), make_query_request({"dryRun": True})),
74-
(
102+
pytest.param(
103+
job_query.QueryJobConfig(dry_run=True),
104+
make_query_request({"dryRun": True}),
105+
id="job_config-with-dry_run",
106+
),
107+
pytest.param(
75108
job_query.QueryJobConfig(use_query_cache=False),
76109
make_query_request({"useQueryCache": False}),
110+
id="job_config-with-use_query_cache",
77111
),
78-
(
112+
pytest.param(
79113
job_query.QueryJobConfig(use_legacy_sql=True),
80114
make_query_request({"useLegacySql": True}),
115+
id="job_config-with-use_legacy_sql",
81116
),
82-
(
117+
pytest.param(
83118
job_query.QueryJobConfig(
84119
query_parameters=[
85120
ScalarQueryParameter("named_param1", "STRING", "param-value"),
@@ -103,8 +138,9 @@ def make_query_response(
103138
],
104139
}
105140
),
141+
id="job_config-with-query_parameters-named",
106142
),
107-
(
143+
pytest.param(
108144
job_query.QueryJobConfig(
109145
query_parameters=[
110146
ScalarQueryParameter(None, "STRING", "param-value"),
@@ -126,8 +162,9 @@ def make_query_response(
126162
],
127163
}
128164
),
165+
id="job_config-with-query_parameters-positional",
129166
),
130-
(
167+
pytest.param(
131168
job_query.QueryJobConfig(
132169
connection_properties=[
133170
ConnectionProperty(key="time_zone", value="America/Chicago"),
@@ -142,14 +179,17 @@ def make_query_response(
142179
]
143180
}
144181
),
182+
id="job_config-with-connection_properties",
145183
),
146-
(
184+
pytest.param(
147185
job_query.QueryJobConfig(labels={"abc": "def"}),
148186
make_query_request({"labels": {"abc": "def"}}),
187+
id="job_config-with-labels",
149188
),
150-
(
189+
pytest.param(
151190
job_query.QueryJobConfig(maximum_bytes_billed=987654),
152191
make_query_request({"maximumBytesBilled": "987654"}),
192+
id="job_config-with-maximum_bytes_billed",
153193
),
154194
),
155195
)
@@ -159,6 +199,19 @@ def test__to_query_request(job_config, expected):
159199
assert result == expected
160200

161201

202+
@pytest.mark.parametrize(
203+
("job_config", "invalid_key"),
204+
(
205+
pytest.param(job_copy.CopyJobConfig(), "copy", id="copy"),
206+
pytest.param(job_extract.ExtractJobConfig(), "extract", id="extract"),
207+
pytest.param(job_load.LoadJobConfig(), "load", id="load"),
208+
),
209+
)
210+
def test__to_query_request_raises_for_invalid_config(job_config, invalid_key):
211+
with pytest.raises(ValueError, match=f"{repr(invalid_key)} in job_config"):
212+
_job_helpers._to_query_request(job_config, query="SELECT 1")
213+
214+
162215
def test__to_query_job_defaults():
163216
mock_client = mock.create_autospec(Client)
164217
response = make_query_response(

0 commit comments

Comments
 (0)