Skip to content

fix(bigquery): push LIMIT into SQL and strip trailing semicolon#2465

Merged
goldmedal merged 5 commits into
Canner:mainfrom
Bartok9:fix/bigquery-strip-semicolon-and-sql-limit
Jul 24, 2026
Merged

fix(bigquery): push LIMIT into SQL and strip trailing semicolon#2465
goldmedal merged 5 commits into
Canner:mainfrom
Bartok9:fix/bigquery-strip-semicolon-and-sql-limit

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • BigQuery query(..., limit=N) now appends LIMIT N after stripping a trailing semicolon instead of relying only on result(max_results=...).
  • dry_run also strips trailing ; for consistent single-statement validation.

Motivation

google.cloud.bigquery max_results controls pagination of the results API; it does not push a limit into the job SQL, so expensive full-table scans still run when GenBI only needs a preview page. Other connectors already push limits into SQL (Athena append-LIMIT path, postgres subquery wrap). Match that pushdown and reuse the terminating-run semicolon strip so SELECT 1; cannot become multi-statement noise.

Verification

  • core/wren/.venv/bin/python -m pytest tests/unit/test_bigquery_semicolon.py -q — 4 passed
  • Mocked client: query("SELECT 1;", limit=5) submits SELECT 1\nLIMIT 5; dry_run sends SELECT 1
  • Did NOT change: credentials/scopes path

License

core/wren/** only (Apache-2.0).

Summary by CodeRabbit

  • Bug Fixes
    • Improved BigQuery query handling by preprocessing SQL to strip trailing semicolons and apply the requested row limit via an in-SQL LIMIT clause when needed.
    • Updated dry-run behavior to ignore trailing semicolons while preserving the existing dry-run configuration.
    • Ensured semicolons within SQL string literals remain unchanged.
  • Tests
    • Added unit tests covering semicolon stripping, LIMIT injection logic, and dry-run SQL/config behavior.

@github-actions github-actions Bot added python Pull requests that update Python code core labels Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

BigQuery SQL submission now strips trailing semicolons, applies provided limits directly in SQL, and preserves dry-run configuration. Unit tests cover query, dry-run, helper behavior, and semicolons inside literals.

Changes

BigQuery SQL preprocessing

Layer / File(s) Summary
SQL preprocessing and connector wiring
core/wren/src/wren/connector/bigquery.py
Adds trailing-semicolon removal and top-level LIMIT handling. query() submits rewritten SQL when limited, while dry_run() strips trailing semicolons.
SQL preprocessing validation
core/wren/tests/unit/test_bigquery_semicolon.py
Tests limited and unlimited queries, dry-run configuration, trailing-semicolon handling, and preservation of semicolons inside literals.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant BigQueryConnector
  participant BigQueryClient
  participant QueryJob
  BigQueryConnector->>BigQueryConnector: Strip trailing semicolon and append LIMIT
  BigQueryConnector->>BigQueryClient: Submit SQL
  BigQueryClient->>QueryJob: Create query job
  BigQueryConnector->>QueryJob: Fetch results
Loading

Suggested reviewers: goldmedal

Poem

A rabbit trimmed SQL’s semicolon tail,
And sent a neat LIMIT on its trail.
Quoted marks stayed tucked away,
While dry runs kept their config in play.
Hop, query, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main BigQuery SQL rewrite and semicolon stripping changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/wren/tests/unit/test_bigquery_semicolon.py (1)

41-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dead fake_bq setup and missing use_query_cache assertion in test_dry_run_strips_trailing_semicolon.

The first patch.dict (line 48) and fake_bq (lines 43–45) are immediately overridden by the second patch.dict (lines 57–63) and have no effect on the test — dry_run is called inside the inner patch where sys.modules["google.cloud.bigquery"] is fake_mod. This dead code is misleading for future maintainers.

Additionally, dry_run() sets use_query_cache=False but the test only asserts dry_run is True. Adding the missing assertion would fully verify the job config contract.

♻️ Proposed cleanup
 def test_dry_run_strips_trailing_semicolon() -> None:
     connector, client = _make_mock_connector()
-    fake_bq = type("BQ", (), {
-        "QueryJobConfig": staticmethod(lambda **kw: type("C", (), kw)()),
-    })
     import sys
     from unittest.mock import patch
-    with patch.dict(sys.modules, {"google": MagicMock(), "google.cloud": MagicMock(), "google.cloud.bigquery": fake_bq}):
-        # Still import-side effect free: inject simple stub
-        import types
-        fake_mod = types.ModuleType("google.cloud.bigquery")
-        class QueryJobConfig:
-            def __init__(self, dry_run=False, use_query_cache=True):
-                self.dry_run = dry_run
-                self.use_query_cache = use_query_cache
-        fake_mod.QueryJobConfig = QueryJobConfig
-        with patch.dict(sys.modules, {
-            "google": types.ModuleType("google"),
-            "google.cloud": types.ModuleType("google.cloud"),
-            "google.cloud.bigquery": fake_mod,
-            "google.oauth2": types.ModuleType("google.oauth2"),
-            "google.oauth2.service_account": types.ModuleType("google.oauth2.service_account"),
-        }):
-            connector.dry_run("SELECT 1;  \n")
+    import types
+    fake_mod = types.ModuleType("google.cloud.bigquery")
+    class QueryJobConfig:
+        def __init__(self, dry_run=False, use_query_cache=True):
+            self.dry_run = dry_run
+            self.use_query_cache = use_query_cache
+    fake_mod.QueryJobConfig = QueryJobConfig
+    with patch.dict(sys.modules, {
+        "google": types.ModuleType("google"),
+        "google.cloud": types.ModuleType("google.cloud"),
+        "google.cloud.bigquery": fake_mod,
+        "google.oauth2": types.ModuleType("google.oauth2"),
+        "google.oauth2.service_account": types.ModuleType("google.oauth2.service_account"),
+    }):
+        connector.dry_run("SELECT 1;  \n")
     (sent,), kwargs = client.query.call_args
     assert sent == "SELECT 1"
     assert kwargs["job_config"].dry_run is True
+    assert kwargs["job_config"].use_query_cache is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren/tests/unit/test_bigquery_semicolon.py` around lines 41 - 67, Remove
the unused fake_bq definition and the redundant outer patch.dict block from
test_dry_run_strips_trailing_semicolon, keeping only the inner module stubs
required by the test. Add an assertion that kwargs["job_config"].use_query_cache
is False to verify the complete dry_run job configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/wren/src/wren/connector/bigquery.py`:
- Around line 25-33: Update _apply_limit to avoid appending a second LIMIT when
the SQL already contains one: detect an existing LIMIT clause and return the
stripped query unchanged, or wrap the original query in a subquery before
applying the outer LIMIT. Preserve trailing-semicolon handling and integer limit
conversion.

---

Nitpick comments:
In `@core/wren/tests/unit/test_bigquery_semicolon.py`:
- Around line 41-67: Remove the unused fake_bq definition and the redundant
outer patch.dict block from test_dry_run_strips_trailing_semicolon, keeping only
the inner module stubs required by the test. Add an assertion that
kwargs["job_config"].use_query_cache is False to verify the complete dry_run job
configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0d75fb5f-70a9-4731-ba4a-19e5724f1942

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba4fae and af0f7c9.

📒 Files selected for processing (2)
  • core/wren/src/wren/connector/bigquery.py
  • core/wren/tests/unit/test_bigquery_semicolon.py

Comment thread core/wren/src/wren/connector/bigquery.py Outdated
@Bartok9

Bartok9 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — applied the cleanup in 82555c6: dropped the dead fake_bq/outer patch.dict block and added the missing assert kwargs["job_config"].use_query_cache is False assertion so the dry-run job config contract is fully verified. All 4 tests still pass.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Bartok9 Thanks for confirming — glad the cleanup landed cleanly and the use_query_cache is False assertion now fully covers the dry-run job config contract. Nice work getting all 4 tests green.

🐇✨

Bartok9 added a commit to Bartok9/WrenAI that referenced this pull request Jul 10, 2026
Avoid second LIMIT on pre-limited SQL (CodeRabbit Canner#2465).
@Bartok9

Bartok9 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit: outer LIMIT already present → no second LIMIT appended.

Bartok9 added a commit to Bartok9/WrenAI that referenced this pull request Jul 10, 2026
Avoid second LIMIT on pre-limited SQL (CodeRabbit Canner#2465).
@Bartok9
Bartok9 force-pushed the fix/bigquery-strip-semicolon-and-sql-limit branch from 96a4ba2 to 665fe09 Compare July 10, 2026 14:51
@goldmedal

Copy link
Copy Markdown
Collaborator

#2478 fixed the building failure of wren-core-py. Could you rebase on main to fix it?

Bartok9 added a commit to Bartok9/WrenAI that referenced this pull request Jul 13, 2026
Avoid second LIMIT on pre-limited SQL (CodeRabbit Canner#2465).
@Bartok9
Bartok9 force-pushed the fix/bigquery-strip-semicolon-and-sql-limit branch from 665fe09 to bc0e9fb Compare July 13, 2026 02:21
@Bartok9

Bartok9 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@goldmedal Done — rebased onto current main (which now includes #2478's wren-core-py build fix). Force-pushed; CI should be green now. Thanks!

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/wren/src/wren/connector/bigquery.py`:
- Around line 40-41: Update the limit-handling logic around _has_outer_limit so
a requested limit cannot exceed the caller’s smaller limit when the SQL already
contains an outer LIMIT. Apply the minimum of the existing and requested limits
or wrap the query with the requested limit, and add a regression test covering
query("... LIMIT 100", limit=5) returning at most five rows.
- Around line 10-22: Update _strip_trailing_semicolon and the _apply_limit flow
to recognize trailing line and block SQL comments, preserving those comments
while removing or relocating a terminal semicolon before appending LIMIT. Ensure
dry_run uses the same normalization so terminal semicolons are removed with
trailing comments present, and add regression tests covering both -- and /* ...
*/ comments.
- Around line 25-28: Replace the raw regex in _has_outer_limit with SQL-aware
tokenization or parsing that ignores comments, string literals, and nested
expressions while identifying only a trailing top-level LIMIT clause. Ensure
both SELECT 1 followed by a comment containing LIMIT and SELECT 1 LIMIT 10
followed by a comment are handled correctly, and add regression tests for these
cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e2da6884-2234-425a-9eac-63177cbf6305

📥 Commits

Reviewing files that changed from the base of the PR and between 665fe09 and bc0e9fb.

📒 Files selected for processing (2)
  • core/wren/src/wren/connector/bigquery.py
  • core/wren/tests/unit/test_bigquery_semicolon.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/wren/tests/unit/test_bigquery_semicolon.py

Comment on lines +10 to +22
_TRAILING_SEMICOLONS_RE = re.compile(r"[;\s]+\Z")


def _strip_trailing_semicolon(sql: str) -> str:
"""Strip trailing ``;`` / whitespace for BigQuery jobs.

BigQuery job submission is more lenient than engines that subquery-wrap,
but a trailing semicolon still breaks when SQL is later composed, and
multi-statement batches with a terminal ``;`` are not the interface we
expose for single-statement GenBI queries. Only the terminating run is
stripped so ``SELECT ';'`` literals remain.
"""
return _TRAILING_SEMICOLONS_RE.sub("", sql)

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Handle trailing SQL comments before appending LIMIT.

For valid input such as SELECT 1; -- note, _apply_limit produces SELECT 1; -- note\nLIMIT 5; the semicolon remains before the appended clause, so the submitted SQL is invalid. dry_run also fails to strip this statement terminator. Preserve trailing comments while removing or relocating the terminal semicolon, and add -- and /* ... */ regression tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren/src/wren/connector/bigquery.py` around lines 10 - 22, Update
_strip_trailing_semicolon and the _apply_limit flow to recognize trailing line
and block SQL comments, preserving those comments while removing or relocating a
terminal semicolon before appending LIMIT. Ensure dry_run uses the same
normalization so terminal semicolons are removed with trailing comments present,
and add regression tests covering both -- and /* ... */ comments.

Comment thread core/wren/src/wren/connector/bigquery.py Outdated
Comment thread core/wren/src/wren/connector/bigquery.py Outdated
Bartok9 added a commit to Bartok9/WrenAI that referenced this pull request Jul 13, 2026
Avoid second LIMIT on pre-limited SQL (CodeRabbit Canner#2465).
@Bartok9
Bartok9 force-pushed the fix/bigquery-strip-semicolon-and-sql-limit branch from bc0e9fb to a8bcdad Compare July 13, 2026 12:09
@goldmedal

Copy link
Copy Markdown
Collaborator

This PR should also change to the shared strip_trailing_semicolon function.

@goldmedal

Copy link
Copy Markdown
Collaborator

@Bartok9, could you modify this PR? I think it should use the shared strip_trailing_semicolon function

Bartok9 added 5 commits July 23, 2026 21:47
max_results only caps result pages; append LIMIT after stripping a
terminal semicolon so the engine short-circuits expensive scans.
… dry_run

Addresses CodeRabbit review: removes the overridden fake_bq/patch.dict block
and verifies dry_run job config disables query cache.
Avoid second LIMIT on pre-limited SQL (CodeRabbit Canner#2465).
Drop fragile outer-LIMIT regex detection; subquery-wrap so nested limitations
and comment-only LIMIT tokens cannot bypass max_results contract. Strip standalone
query trailing semicolon; keep dry_run cleanup.
Drop the local _strip_trailing_semicolon duplicate and import the shared
strip_trailing_semicolon from wren.connector.base, matching the other
connectors (redshift/trino/mssql/clickhouse). Hoist test imports to module
top to satisfy ruff PLC0415.
@Bartok9
Bartok9 force-pushed the fix/bigquery-strip-semicolon-and-sql-limit branch from a8bcdad to 4670ae1 Compare July 24, 2026 01:48
@Bartok9

Bartok9 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@goldmedal Done — switched to the shared strip_trailing_semicolon from wren.connector.base (dropped the local duplicate), matching redshift/trino/mssql/clickhouse. Also rebased onto current main and cleaned up the test imports. All unit tests pass and ruff is green. Thanks!

@goldmedal goldmedal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @Bartok9

@goldmedal
goldmedal merged commit 0a25df3 into Canner:main Jul 24, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants