Skip to content

Commit c54ec01

Browse files
committed
fix: detect the location of INFORMATION_SCHEMA views in read_gbq
When no location is set, bigframes.pandas.read_gbq() and read_gbq_table() look up the table to pick the session location. That failed for INFORMATION_SCHEMA views: tables.get can't look them up, and 4-part IDs such as project.region-us.INFORMATION_SCHEMA.SCHEMATA were sent to the pyiceberg REST catalog. Get the location from a dry run query on the view instead, the same way the session loader gets metadata for these views. BUG=570647343 Change-Id: I01b9d07e25c29f8315f54f9036393d30891e2ab9 Reviewed-on: https://bigframes-internal-review.git.corp.google.com/c/bigframes/+/4820 Kokoro-System: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com> Kokoro-Unit: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com> Kokoro-Docs: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com> Kokoro-E2E: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com> Reviewed-by: Tim Swena <swast@google.com> Kokoro-Doctest: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com> Kokoro-Lint: Kokoro <noreply+kokoro-dedicatedkokoro-dedicated Kokorogoogle.com>
1 parent 54b1259 commit c54ec01

3 files changed

Lines changed: 99 additions & 0 deletions

File tree

‎bigframes/pandas/io/api.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ def _set_default_session_location_if_possible_deferred_query(create_query):
687687
# Address circular imports in doctest due to bigframes/session/__init__.py
688688
# containing a lot of logic and samples.
689689
from bigframes.session._io import bigquery
690+
from bigframes.session._io.bigquery import read_gbq_table as bf_read_gbq_table
690691

691692
# Set the location as per the query if this is the first query the user is
692693
# running and:
@@ -715,6 +716,18 @@ def _set_default_session_location_if_possible_deferred_query(create_query):
715716
# aren't necessary.
716717
job = _dry_run(query, bqclient)
717718
config.options.bigquery.location = job.location
719+
elif bf_read_gbq_table.is_information_schema(query):
720+
# INFORMATION_SCHEMA views can't be looked up with tables.get, and
721+
# 4-part IDs such as project.region-us.INFORMATION_SCHEMA.SCHEMATA
722+
# look like Iceberg table IDs. Instead, dry run a query on the view
723+
# and use the location of that dry run job. The session loader
724+
# gets the metadata for these views the same way.
725+
table = bf_read_gbq_table.get_information_schema_metadata(
726+
bqclient=bqclient,
727+
table_id=query,
728+
default_project=default_project,
729+
)
730+
config.options.bigquery.location = table.location
718731
elif bq_data.is_irc_table(query):
719732
irc_table = bigframes.session.iceberg.get_table(
720733
default_project, query, bqclient._credentials

‎tests/system/small/pandas/test_read_gbq_information_schema.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import pytest
1616

17+
import bigframes.pandas as bpd
18+
1719

1820
@pytest.mark.parametrize("include_project", [True, False])
1921
@pytest.mark.parametrize(
@@ -48,3 +50,18 @@ def test_read_gbq_schemata_four_parts_can_be_peeked(unordered_session):
4850
)
4951
result = df.peek()
5052
assert result is not None
53+
54+
55+
@pytest.mark.parametrize("include_project", [True, False])
56+
def test_bpd_read_gbq_information_schema_sets_location(
57+
reset_default_session_and_location, project_id: str, include_project: bool
58+
):
59+
view_id = "region-US.INFORMATION_SCHEMA.SCHEMATA"
60+
if include_project:
61+
view_id = f"{project_id}.{view_id}"
62+
assert not bpd.options.bigquery.location
63+
64+
df = bpd.read_gbq(view_id, max_results=10)
65+
66+
assert df.dtypes is not None
67+
assert bpd.options.bigquery.location == "US"

‎tests/unit/pandas/io/test_api.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,72 @@ def test_read_gbq_colab_calls_set_location(
127127
assert kwargs["pyformat_args"] == sample_pyformat_args
128128
assert not kwargs["dry_run"]
129129
assert isinstance(result, bigframes.dataframe.DataFrame)
130+
131+
132+
@pytest.mark.parametrize(
133+
"table_id",
134+
[
135+
# 4 parts, the same shape as an Iceberg REST catalog table ID.
136+
"my-project.region-us.INFORMATION_SCHEMA.SCHEMATA",
137+
"my-project.my_dataset.INFORMATION_SCHEMA.TABLES",
138+
# 3 parts, which tables.get would parse as project.dataset.table.
139+
"region-us.INFORMATION_SCHEMA.SCHEMATA",
140+
"my_dataset.INFORMATION_SCHEMA.TABLES",
141+
],
142+
)
143+
@mock.patch("bigframes.pandas.io.api._get_bqclient_and_project")
144+
def test_set_default_session_location_information_schema_uses_dry_run(
145+
mock_get_bqclient_and_project, table_id
146+
):
147+
bigframes.pandas.close_session()
148+
bigframes.pandas.options.bigquery.location = None
149+
mock_bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True)
150+
mock_query_job = mock.create_autospec(google.cloud.bigquery.QueryJob, instance=True)
151+
mock_query_job.location = "us-east4"
152+
type(mock_query_job).schema = mock.PropertyMock(return_value=[])
153+
mock_bqclient.query.return_value = mock_query_job
154+
mock_get_bqclient_and_project.return_value = (mock_bqclient, "default-project")
155+
156+
try:
157+
bf_io_api._set_default_session_location_if_possible(table_id)
158+
159+
mock_bqclient.query.assert_called_once()
160+
args, kwargs = mock_bqclient.query.call_args
161+
assert table_id in args[0]
162+
assert kwargs["job_config"].dry_run
163+
mock_bqclient.get_table.assert_not_called()
164+
assert bigframes.pandas.options.bigquery.location == "us-east4"
165+
finally:
166+
bigframes.pandas.options.bigquery.location = None
167+
168+
169+
@pytest.mark.parametrize(
170+
"table_id",
171+
[
172+
"my-project.my_dataset.my_table",
173+
# Names that contain INFORMATION_SCHEMA but aren't INFORMATION_SCHEMA
174+
# views.
175+
"my-project.MY_INFORMATION_SCHEMA.TABLES",
176+
"my-project.my_dataset.INFORMATION_SCHEMA",
177+
],
178+
)
179+
@mock.patch("bigframes.pandas.io.api._get_bqclient_and_project")
180+
def test_set_default_session_location_table_uses_get_table(
181+
mock_get_bqclient_and_project, table_id
182+
):
183+
bigframes.pandas.close_session()
184+
bigframes.pandas.options.bigquery.location = None
185+
mock_bqclient = mock.create_autospec(google.cloud.bigquery.Client, instance=True)
186+
mock_table = mock.create_autospec(google.cloud.bigquery.Table, instance=True)
187+
mock_table.location = "asia-northeast1"
188+
mock_bqclient.get_table.return_value = mock_table
189+
mock_get_bqclient_and_project.return_value = (mock_bqclient, "default-project")
190+
191+
try:
192+
bf_io_api._set_default_session_location_if_possible(table_id)
193+
194+
mock_bqclient.get_table.assert_called_once_with(table_id)
195+
mock_bqclient.query.assert_not_called()
196+
assert bigframes.pandas.options.bigquery.location == "asia-northeast1"
197+
finally:
198+
bigframes.pandas.options.bigquery.location = None

0 commit comments

Comments
 (0)