Skip to content

Commit e9f0e3f

Browse files
authored
fix: avoid TypeError when executing DML statements with read_gbq (#483)
1 parent 96fe769 commit e9f0e3f

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
lines changed

pandas_gbq/gbq.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ def run_query(self, query, max_results=None, progress_bar_type=None, **kwargs):
410410
from concurrent.futures import TimeoutError
411411
from google.auth.exceptions import RefreshError
412412
from google.cloud import bigquery
413+
import pandas
413414

414415
job_config = {
415416
"query": {
@@ -495,6 +496,11 @@ def run_query(self, query, max_results=None, progress_bar_type=None, **kwargs):
495496
except self.http_error as ex:
496497
self.process_http_error(ex)
497498

499+
# Avoid attempting to download results from DML queries, which have no
500+
# destination.
501+
if query_reply.destination is None:
502+
return pandas.DataFrame()
503+
498504
rows_iter = self.client.list_rows(
499505
query_reply.destination, max_results=max_results
500506
)

tests/system/test_read_gbq.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import collections
66
import datetime
77
import decimal
8+
import random
89

910
import db_dtypes
11+
from google.cloud import bigquery
1012
import pandas
1113
import pandas.testing
1214
import pytest
@@ -21,6 +23,21 @@
2123
)
2224

2325

26+
@pytest.fixture
27+
def writable_table(
28+
bigquery_client: bigquery.Client, project_id: str, random_dataset: bigquery.Dataset
29+
):
30+
full_table_id = f"{project_id}.{random_dataset.dataset_id}.writable_table_{random.randrange(1_000_000_000)}"
31+
table = bigquery.Table(full_table_id)
32+
table.schema = [
33+
bigquery.SchemaField("field1", "STRING"),
34+
bigquery.SchemaField("field2", "INTEGER"),
35+
]
36+
bigquery_client.create_table(table)
37+
yield full_table_id
38+
bigquery_client.delete_table(full_table_id)
39+
40+
2441
@pytest.mark.parametrize(["use_bqstorage_api"], [(True,), (False,)])
2542
@pytest.mark.parametrize(
2643
["query", "expected", "use_bqstorage_apis"],
@@ -605,3 +622,16 @@ def test_empty_dataframe(read_gbq, use_bqstorage_api):
605622
)
606623
result = read_gbq(query, use_bqstorage_api=use_bqstorage_api)
607624
pandas.testing.assert_frame_equal(result, expected, check_index_type=False)
625+
626+
627+
def test_dml_query(read_gbq, writable_table: str):
628+
query = f"""
629+
UPDATE `{writable_table}`
630+
SET field1 = NULL
631+
WHERE field1 = 'string';
632+
UPDATE `{writable_table}`
633+
SET field2 = NULL
634+
WHERE field2 < 0;
635+
"""
636+
result = read_gbq(query)
637+
assert result is not None

tests/unit/test_gbq.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,23 @@ def mock_get_credentials_no_project(*args, **kwargs):
3232
return mock_credentials, None
3333

3434

35-
@pytest.fixture(autouse=True)
36-
def default_bigquery_client(mock_bigquery_client):
35+
@pytest.fixture
36+
def mock_query_job():
3737
mock_query = mock.create_autospec(google.cloud.bigquery.QueryJob)
3838
mock_query.job_id = "some-random-id"
3939
mock_query.state = "DONE"
40+
return mock_query
41+
42+
43+
@pytest.fixture(autouse=True)
44+
def default_bigquery_client(mock_bigquery_client, mock_query_job):
4045
mock_rows = mock.create_autospec(google.cloud.bigquery.table.RowIterator)
4146
mock_rows.total_rows = 1
4247

4348
mock_rows.__iter__.return_value = [(1,)]
44-
mock_query.result.return_value = mock_rows
49+
mock_query_job.result.return_value = mock_rows
4550
mock_bigquery_client.list_rows.return_value = mock_rows
46-
mock_bigquery_client.query.return_value = mock_query
51+
mock_bigquery_client.query.return_value = mock_query_job
4752

4853
# Mock out SELECT 1 query results.
4954
def generate_schema():
@@ -718,3 +723,15 @@ def test_read_gbq_with_list_rows_error_translates_exception(
718723
)
719724
def test_query_response_bytes(size_in_bytes, formatted_text):
720725
assert gbq.GbqConnector.sizeof_fmt(size_in_bytes) == formatted_text
726+
727+
728+
def test_run_query_with_dml_query(mock_bigquery_client, mock_query_job):
729+
"""
730+
Don't attempt to download results from a DML query / query with no results.
731+
732+
https://github.com/googleapis/python-bigquery-pandas/issues/481
733+
"""
734+
connector = _make_connector()
735+
type(mock_query_job).destination = mock.PropertyMock(return_value=None)
736+
connector.run_query("UPDATE tablename SET value = '';")
737+
mock_bigquery_client.list_rows.assert_not_called()

0 commit comments

Comments
 (0)