Skip to content

fix(pandas-gbq): keep the dialect when configuration has a query section - #18775

Open
abhishekacharya200 wants to merge 1 commit into
googleapis:mainfrom
abhishekacharya200:fix/pandas-gbq-dialect-with-query-config
Open

abhishekacharya200 wants to merge 1 commit into
googleapis:mainfrom
abhishekacharya200:fix/pandas-gbq-dialect-with-query-config

Conversation

@abhishekacharya200

Copy link
Copy Markdown
Contributor

Fixes #18774

read_gbq takes a configuration for extra query options, and its docstring gives {'query': {'useQueryCache': False}} as the example. GbqConnector.run_query builds {"query": {"useLegacySql": self.dialect == "legacy"}} from the dialect and then calls job_config_dict.update(configuration), so a configuration with its own "query" section replaces that whole section. useLegacySql is dropped, and google-cloud-bigquery runs the query as Standard SQL even with dialect="legacy" or pandas_gbq.context.dialect = "legacy". The same happens when the query text is passed inside configuration, because read_gbq takes the text out and leaves an empty "query" section behind.

This merges the "query" section of configuration into the default one instead of replacing it. An explicit useLegacySql in configuration still takes precedence, so only the case where it is missing changes.

With the stand in server from the issue, which prints the options each query sends:

read_gbq call main this branch
dialect='legacy' useLegacySql=True useLegacySql=True
dialect='legacy', configuration={'query': {'useQueryCache': False}} useLegacySql=False useLegacySql=True
dialect='standard', configuration={'query': {'useQueryCache': False}} useLegacySql=False useLegacySql=False
read_gbq(None, dialect='legacy', configuration={'query': {'query': 'SELECT 1 AS x'}}) useLegacySql=False useLegacySql=True

useQueryCache=False reaches the server in both versions, so the other options from configuration keep working as before.

Added test_read_gbq_with_configuration_query_section_keeps_dialect, test_read_gbq_with_configuration_query_section_keeps_context_dialect and test_read_gbq_with_query_in_configuration_keeps_dialect, which fail on main and pass with this change, and test_read_gbq_with_configuration_use_legacy_sql_overrides_dialect, which documents that an explicit useLegacySql still wins. The full tests/unit suite passes locally (253 passed, 1 skipped), and the noxfile's ruff==0.14.14 import and format checks and flake8 report nothing.

run_query built {"query": {"useLegacySql": ...}} from the dialect and then
called job_config_dict.update(configuration). A configuration with its own
"query" section, such as the {"query": {"useQueryCache": False}} example in
the read_gbq docs, replaced that whole section, so useLegacySql was dropped
and google-cloud-bigquery ran the query as Standard SQL even with
dialect="legacy".

Merge the "query" section instead. An explicit useLegacySql in the
configuration still takes precedence.
@abhishekacharya200
abhishekacharya200 requested review from a team as code owners October 20, 2026 04:12
@abhishekacharya200
abhishekacharya200 requested review from julianxu-goog and removed request for a team October 20, 2026 04:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates gbq_connector.py to merge the user-provided "query" configuration with the existing query configuration instead of completely replacing it, ensuring that settings like the SQL dialect are preserved. It also adds comprehensive unit tests to verify this behavior. The reviewer identified a potential TypeError that could occur if the "query" key in the configuration is explicitly set to None, and provided a code suggestion to safely handle this scenario.

Comment on lines +203 to +205
query_config = {**job_config_dict["query"], **config.get("query", {})}
job_config_dict.update(config)
job_config_dict["query"] = query_config

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If configuration contains a "query" key with a value of None, config.get("query", {}) returns None instead of the default {}. This causes a TypeError when attempting to unpack it with **. Please ensure that configuration values are validated or handled appropriately to maintain class design consistency and ensure fail-fast behavior.

Suggested change
query_config = {**job_config_dict["query"], **config.get("query", {})}
job_config_dict.update(config)
job_config_dict["query"] = query_config
config_query = config.get("query")
if isinstance(config_query, dict):
query_config = {**job_config_dict["query"], **config_query}
job_config_dict.update(config)
job_config_dict["query"] = query_config
else:
job_config_dict.update(config)
References
  1. Setters should strictly validate that all values in a dictionary are of the expected type, and handle None values appropriately, to maintain class design consistency and ensure fail-fast behavior.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pandas-gbq: read_gbq ignores dialect='legacy' when configuration has a query section

1 participant