Skip to content

fix(athena): push LIMIT into SQL and strip trailing semicolon on wrap - #2457

Merged
goldmedal merged 4 commits into
Canner:mainfrom
Bartok9:fix/athena-pushdown-limit
Jul 14, 2026
Merged

fix(athena): push LIMIT into SQL and strip trailing semicolon on wrap#2457
goldmedal merged 4 commits into
Canner:mainfrom
Bartok9:fix/athena-pushdown-limit

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Push query(..., limit=N) into Athena SQL via SELECT * FROM (<sql>) AS _wren_sub LIMIT N.
  • Strip a trailing semicolon before wrapping so ;-terminated user SQL cannot break composition.

Motivation

Previously the connector ran the full statement and used table.slice(0, limit). Athena (Presto/Trino-flavoured) can stop early when LIMIT is in the plan; client-side slicing paid full scan + transfer cost. Same pattern as redshift/postgres/clickhouse/snowflake pushdown helpers.

Verification

  • PYTHONPATH=src pytest tests/unit/test_athena_limit_pushdown.py3 passed
  • Asserted executed SQL for SELECT 1; + limit=3 is
    SELECT * FROM (SELECT 1) AS _wren_sub LIMIT 3
  • Without limit, original SQL is executed unchanged.

License

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

Summary by CodeRabbit

  • Bug Fixes

    • Improved Athena query execution by pushing limit into the generated SQL via subquery wrapping and appending LIMIT <n> (instead of slicing results in Python).
    • Clean input SQL by removing only trailing semicolons/extra whitespace; trailing -- comments are handled safely.
    • Enhanced error reporting by recording the exact composed SQL that was executed.
  • Tests

    • Added coverage for trailing semicolon stripping (preserving semicolons inside quoted strings) and correct handling of trailing line comments.
    • Expanded Athena connector tests to verify SQL wrapping/LIMIT pushdown and that returned Arrow tables respect the requested row count.

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

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 89c8de00-7fb1-45bf-bf07-5e9e8f586094

📥 Commits

Reviewing files that changed from the base of the PR and between 969298c and 895460b.

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

Walkthrough

AthenaConnector.query now pushes LIMIT into Athena by wrapping limited SQL in a subquery after trimming trailing semicolons and whitespace. Error metadata references the composed SQL, and tests cover limited, unlimited, quoted-semicolon, and comment cases.

Changes

Athena LIMIT Pushdown

Layer / File(s) Summary
Query method LIMIT pushdown implementation
core/wren/src/wren/connector/athena.py
query composes and executes wrapped SQL for limited requests, returns the Athena-produced table without Python slicing, and records the executed SQL in WrenError metadata.
Unit tests for LIMIT pushdown behavior
core/wren/tests/unit/test_athena_limit_pushdown.py, core/wren/tests/unit/test_athena_connector.py
Tests cover semicolon stripping, limited and unlimited execution, trailing comments, Arrow table construction, and the updated limited-row result.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AthenaConnector
  participant AthenaCursor

  Client->>AthenaConnector: query(sql, limit)
  AthenaConnector->>AthenaConnector: strip trailing semicolon
  AthenaConnector->>AthenaConnector: wrap SQL with LIMIT
  AthenaConnector->>AthenaCursor: execute(executed)
  AthenaCursor-->>AthenaConnector: result table
  AthenaConnector-->>Client: return Arrow table
Loading

Possibly related PRs

  • Canner/WrenAI#2407: Uses the same trailing-semicolon stripping and subquery LIMIT pushdown pattern.
  • Canner/WrenAI#2421: Modifies the Athena connector’s SQL composition and trailing-semicolon handling.
  • Canner/WrenAI#2456: Adds analogous connector LIMIT pushdown tests, including trailing comment handling.

Suggested reviewers: goldmedal

Poem

A rabbit hops where limits land,
Athena runs the SQL as planned.
Semicolons tuck their tails away,
Two neat rows return today.
Tests thump softly: all is bright! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 Athena change: SQL LIMIT pushdown with trailing-semicolon stripping.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

18-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an error-path test for the DIALECT_SQL metadata change.

The PR changes WrenError metadata from DIALECT_SQL: sql to DIALECT_SQL: executed (line 339 in athena.py), but no test verifies this. When cursor.execute raises, the resulting WrenError should carry the composed subquery-wrapped SQL, not the original input.

♻️ Suggested error-path test
 def test_query_without_limit_runs_original_sql():
     connector = AthenaConnector.__new__(AthenaConnector)
     connector.connection = MagicMock()
     cursor = MagicMock()
     cursor.close = MagicMock()
     with patch(
         "wren.connector.athena._build_athena_arrow_table", return_value=MagicMock()
     ), patch("wren.connector.athena.contextlib.closing", side_effect=lambda c: _CM(c)):
         connector.connection.cursor.return_value = cursor
         connector.query("SELECT 1")
     cursor.execute.assert_called_once_with("SELECT 1")


+def test_query_error_metadata_reflects_executed_sql():
+    from wren.errors import WrenError
+
+    connector = AthenaConnector.__new__(AthenaConnector)
+    connector.connection = MagicMock()
+    cursor = MagicMock()
+    cursor.close = MagicMock()
+    cursor.execute.side_effect = RuntimeError("boom")
+    connector.connection.cursor.return_value = cursor
+
+    with patch("wren.connector.athena.contextlib.closing", side_effect=lambda c: _CM(c)):
+        with pytest.raises(WrenError) as exc_info:
+            connector.query("SELECT 1;", limit=3)
+
+    assert exc_info.value.metadata["DIALECT_SQL"] == (
+        "SELECT * FROM (SELECT 1) AS _wren_sub LIMIT 3"
+    )
🤖 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_athena_limit_pushdown.py` around lines 18 - 38, The
Athena query path needs an error-path unit test to verify the `DIALECT_SQL`
metadata now uses the executed SQL. Extend the existing
`test_query_pushes_limit_into_sql` style in `test_athena_limit_pushdown.py` by
making `cursor.execute` raise and asserting `AthenaConnector.query` raises a
`WrenError` whose metadata comes from the wrapped subquery SQL produced inside
`AthenaConnector.query`, not the original input SQL. Use the existing
`AthenaConnector`, `cursor.execute`, and `WrenError` symbols to locate the code
path and ensure the test checks the composed SQL string is attached under
`DIALECT_SQL`.
🤖 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.

Nitpick comments:
In `@core/wren/tests/unit/test_athena_limit_pushdown.py`:
- Around line 18-38: The Athena query path needs an error-path unit test to
verify the `DIALECT_SQL` metadata now uses the executed SQL. Extend the existing
`test_query_pushes_limit_into_sql` style in `test_athena_limit_pushdown.py` by
making `cursor.execute` raise and asserting `AthenaConnector.query` raises a
`WrenError` whose metadata comes from the wrapped subquery SQL produced inside
`AthenaConnector.query`, not the original input SQL. Use the existing
`AthenaConnector`, `cursor.execute`, and `WrenError` symbols to locate the code
path and ensure the test checks the composed SQL string is attached under
`DIALECT_SQL`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3b846a6e-f353-4929-b13d-26209585e7f1

📥 Commits

Reviewing files that changed from the base of the PR and between c4de8ac and b52894a.

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

@goldmedal

Copy link
Copy Markdown
Collaborator

Nice cleanup — pushing LIMIT server-side brings Athena in line with the other pushdown connectors (postgres/trino/redshift/clickhouse), and dropping the table.slice() is the right call. Core change looks correct and the tests are solid.

One thing worth fixing before merge:

Single-line wrap is broken by a trailing -- line comment. Athena is Presto/Trino-flavored, so -- line comments run to end-of-line. With the current wrap:

query("SELECT 1 -- pick", limit=3)
→ "SELECT * FROM (SELECT 1 -- pick) AS _wren_sub LIMIT 3"

everything after -- is commented out, leaving SELECT * FROM (SELECT 1 — unbalanced parens / syntax error. _strip_trailing_semicolon doesn't help here (there's no trailing ;).

This is exactly the case flagged on the sibling Snowflake PR (#2456), which fixed it by putting the inner SQL on its own line and adding a regression test. Applying the same guard here keeps the two PRs consistent:

executed = (
    "SELECT * FROM (\n"
    f"{_strip_trailing_semicolon(sql)}\n"
    f") AS _wren_sub LIMIT {int(limit)}"
)

plus a small test mirroring test_query_limit_survives_trailing_line_comment from the Snowflake PR.

For fairness: the single-line form is shared by several existing connectors, so this fragility is pre-existing across the family — but within this Snowflake+Athena series it's an avoidable inconsistency, and a trailing comment in generated SQL isn't far-fetched.

Non-blocking, but a thought for a follow-up: _strip_trailing_semicolon + the wrap expression are now duplicated across ~8 connectors with cosmetic drift (alias name, newline-or-not, {limit} vs {int(limit)}). A single wrap_with_limit(sql, limit) helper in connector/base.py would unify the comment-safety and injection-safety behavior in one place.

Bartok9 added 3 commits July 13, 2026 08:09
query(limit=N) previously executed the full Athena statement and sliced
the Arrow table in Python, so Presto/Trino engines scanned and returned
the whole result even for page/sample calls. Wrap user SQL as a subquery
with LIMIT and strip a trailing semicolon so composition stays valid.
query(limit=N) now wraps SQL with LIMIT rather than post-slicing the
Arrow table. Align the preexisting mock test with that contract.
goldmedal review on Canner#2457 — same newline guard as Snowflake Canner#2456 so
query("SELECT 1 -- pick", limit=n) does not comment out the closing wrap.
@Bartok9
Bartok9 force-pushed the fix/athena-pushdown-limit branch from a51f834 to 969298c Compare July 13, 2026 12:09

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

Review

Verdict: sound change, safe to merge after fixing one misleading comment. The core swap — full result + table.slice(0, limit) → wrapping in SELECT * FROM (<sql>) AS _wren_sub LIMIT N — is correct, well-tested, and consistent with the existing pushdown helpers in postgres/trino/clickhouse/duckdb/redshift/canner/datafusion/oracle/databricks. No public API / MDL / schema surface is touched, and the fix: prefix is valid for release-please. Athena is AWS-only and can't be testcontainerized, so unit-only coverage is the right call.

Findings (severity order)

1. [medium — accuracy] The new inline comment references a Snowflake guard that doesn't exist.
The wrap block comments ... same guard as Snowflake #2456, but snowflake.py still does client-side arrow_table.slice(0, limit) and has no _strip_trailing_semicolon helper and no subquery-LIMIT wrapping — there is no analogous guard there. Additionally, Athena is the only connector using a multiline wrap ((\n{sql}\n)); every sibling is single-line, so the shapes wouldn't match even if Snowflake did push down. The cross-reference is incorrect and will send the next maintainer on a dead-end. Please drop it and keep only the real justification (see #2).

2. [low — style/consistency] Reviewer handle + PR numbers baked into permanent source.
No sibling connector embeds PR numbers or reviewer names in its wrap comment. These references go stale immediately and read as review-thread residue. The substantive reason, though, is genuinely good and worth keeping — suggest replacing the whole parenthetical with something like:

Multiline wrap so a trailing -- line comment in the inner SQL is terminated by the newline instead of swallowing the closing ) AS _wren_sub LIMIT n. (Single-line sibling connectors don't guard this.)

3. [low — tests] test_athena_limit_pushdown.py over-mocks and duplicates existing coverage.
It bypasses __init__ via __new__, hand-rolls a _CM context manager, and patches contextlib.closing — brittle against refactors. And test_query_pushes_limit_into_sql asserts the same wrapped SQL that test_connector_query_returns_arrow_table_and_respects_limit already asserts in test_athena_connector.py using that file's existing _make_cursor harness. The one net-new case worth keeping — test_query_limit_survives_trailing_line_comment — could live in the existing file and reuse that harness instead of standing up a parallel mocking style.

4. [nit — pre-existing, cross-connector] ; followed by a trailing comment isn't stripped.
_TRAILING_SEMICOLONS_RE = r"[;\s]+\Z" only anchors at the end, so SELECT 1; -- foo keeps the interior ; and the wrap becomes invalid Trino SQL. Shared by every sibling helper and not realistic planner output — noting for completeness, not asking for a change here.

Things done right (no action)

  • LIMIT {int(limit)} casts to int, closing the injection vector — safer than siblings that interpolate {limit} raw.
  • if limit is not None correctly handles limit=0 (empty table) rather than the buggy if limit:.
  • Error metadata switched to the executed SQL, so failures show what actually ran.
  • No ORDER-BY determinism regression: the old slice(0, N) on an unordered result was equally non-deterministic; this is a preview-row cap, not a semantic limit.

Drop the incorrect Snowflake Canner#2456 cross-reference (snowflake.py has no
subquery-LIMIT wrap / semicolon guard) and the review-thread residue
(PR numbers, reviewer handle). Keep only the substantive justification:
multiline wrap so a trailing line comment can't swallow the closing wrap.
@Bartok9

Bartok9 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @goldmedal — addressed the review.

Fixed the misleading comment (findings #1 + #2). Dropped the incorrect Snowflake #2456 cross-reference — you're right, snowflake.py still does client-side arrow_table.slice(0, limit) with no _strip_trailing_semicolon and no subquery wrap, so there's no analogous guard there. Also removed the PR-number/reviewer-handle residue. Replaced with just the substantive reason, using your suggested wording:

# Multiline wrap so a trailing `-- line comment` in the inner SQL
# is terminated by the newline instead of swallowing the closing
# `) AS _wren_sub LIMIT n`. (Single-line sibling connectors don't guard this.)

On the non-blocking items:

  • ask_details pipeline evaluation process #3 (test over-mocking / duplication): happy to fold the trailing-comment case into test_athena_connector.py using the existing _make_cursor harness and drop the parallel mocking file in a follow-up if you'd prefer that here rather than a separate cleanup.
  • Refine evaluation process #4 (; + trailing comment) and the wrap_with_limit() base helper: agree both are cross-connector and best as a dedicated refactor unifying all ~8 sibling helpers, rather than growing this Athena-scoped PR.

Pushed as 895460b.

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

👍

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