fix(trino): emit OFFSET before LIMIT for paginated queries - #42899
fix(trino): emit OFFSET before LIMIT for paginated queries#42899eschutho wants to merge 1 commit into
Conversation
Drill to Detail pagination (and any paginated query) fails on Trino/Presto with `mismatched input 'OFFSET'` on page 2+. Superset builds the query with SQLAlchemy `.limit()`/`.offset()`, and the compiled clause order is decided by the driver's dialect. The official `trino` package and `sqlalchemy-trino` override `limit_clause` to emit `OFFSET ... LIMIT`, but PyHive's Presto/Trino dialects inherit SQLAlchemy's ANSI `LIMIT ... OFFSET` ordering, which Presto and Trino reject. Connections that resolve to a PyHive dialect (and any vanilla `presto://` connection) therefore produce invalid SQL when an offset is applied. Guarantee the ordering in Superset instead of relying on the driver: add an `offset_before_limit` engine-spec flag (True for Presto/Trino) and normalize compiled SQL via sqlglot's dialect-aware generator in `compile_sqla_query`. The reorder only runs when the flag is set and the SQL is in the invalid order, so drivers that already emit OFFSET first are untouched. Reported when using Trino as the query engine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code Review Agent Run #42f9d6Actionable Suggestions - 0Filtered by Review RulesBito filtered these suggestions based on rules created automatically for your feedback. Manage rules.
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #42899 +/- ##
==========================================
- Coverage 73.55% 66.37% -7.18%
==========================================
Files 1928 2856 +928
Lines 83529 161153 +77624
Branches 27227 37063 +9836
==========================================
+ Hits 61439 106969 +45530
- Misses 22090 52160 +30070
- Partials 0 2024 +2024
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| # Presto and Trino require OFFSET to appear before LIMIT in the SQL grammar. | ||
| # Superset normalizes compiled SQL accordingly so pagination works even when | ||
| # the connection resolves to a driver whose dialect emits the ANSI | ||
| # ``LIMIT ... OFFSET`` ordering (e.g. PyHive). See | ||
| # ``BaseEngineSpec.apply_offset_before_limit``. | ||
| offset_before_limit = True |
There was a problem hiding this comment.
Suggestion: The flag is defined on PrestoBaseEngineSpec, but HiveEngineSpec inherits PrestoEngineSpec, and SparkEngineSpec and DatabricksHiveEngineSpec inherit HiveEngineSpec. Consequently, every compiled Hive, Spark, and Databricks Interactive Cluster query with LIMIT and OFFSET is reparsed and regenerated using that engine's sqlglot dialect, even though this change is intended only for Presto and Trino. This can rewrite valid engine-specific SQL or alter function semantics; define the flag only on PrestoEngineSpec and TrinoEngineSpec, or explicitly disable it on the Hive-family descendants. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Hive pagination queries receive unintended SQL reformatting.
- ⚠️ Spark pagination queries use the shared normalization path.
- ⚠️ Databricks interactive-cluster SQL is reparsed unnecessarily.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** superset/db_engine_specs/presto.py
**Line:** 170:175
**Comment:**
*Api Mismatch: The flag is defined on `PrestoBaseEngineSpec`, but `HiveEngineSpec` inherits `PrestoEngineSpec`, and `SparkEngineSpec` and `DatabricksHiveEngineSpec` inherit `HiveEngineSpec`. Consequently, every compiled Hive, Spark, and Databricks Interactive Cluster query with `LIMIT` and `OFFSET` is reparsed and regenerated using that engine's sqlglot dialect, even though this change is intended only for Presto and Trino. This can rewrite valid engine-specific SQL or alter function semantics; define the flag only on `PrestoEngineSpec` and `TrinoEngineSpec`, or explicitly disable it on the Hive-family descendants.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
The flagged issue is correct. The To resolve this, you should move the flag definition from Proposed Fix
Would you like me to fetch all other comments on this PR to validate and implement fixes for them as well? superset/db_engine_specs/presto.py |
SUMMARY
Drill to Detail pagination fails on Trino/Presto: navigating to page 2 of the results produces a query that Trino rejects with a syntax error:
This was reported when using Trino as the query engine. The same query runs fine in SQL Lab, because SQL Lab never emits an
OFFSET— only the automated pagination path does.Problem
Superset builds paginated queries with SQLAlchemy's dialect-aware construction (
qry.limit(row_limit).offset(row_offset)inmodels/helpers.py) and compiles them inDatabase.compile_sqla_query. In SQLAlchemy the compiled clause order is decided entirely by the driver's dialect (limit_clause), not by the order the methods are called.Trino and Presto require
OFFSETto appear beforeLIMIT(unlike the ANSILIMIT ... OFFSETordering). Whether Superset emits valid SQL therefore depends on which driver the connection resolves to:trinopackage and the legacysqlalchemy-trinopackage both overridelimit_clauseto emitOFFSET ... LIMIT→ correct.PrestoCompiler/TrinoCompiler) do not overridelimit_clause; they inherit SQLAlchemy's ANSILIMIT ... OFFSET→ rejected by Presto/Trino.So any connection that resolves to a PyHive dialect — including any vanilla
presto://connection on stock Superset — produces invalid SQL as soon as an offset is applied (Drill to Detail page 2+, server-side pagination, etc.).Verified directly:
Fix
Guarantee the ordering in Superset rather than depending on the driver:
offset_before_limitengine-spec flag (defaultFalse, set toTrueonPrestoBaseEngineSpec, inherited by both Presto and Trino).BaseEngineSpec.apply_offset_before_limit(sql), which re-renders the statement through sqlglot's dialect-aware generator (which ordersOFFSETbeforeLIMITfor these dialects). A cheap textual gate means it only runs when the flag is set and the SQL is actually in the invalid order, so drivers that already emitOFFSETfirst are left byte-for-byte untouched.Database.compile_sqla_query, the single choke point where datasource queries are turned into SQL.This fixes every automated-offset path (Drill to Detail, samples, server pagination) for Presto and Trino, independent of the installed SQLAlchemy driver.
BEFORE/AFTER
This is a query-generation fix, so the meaningful before/after is the SQL Superset dispatches for Drill to Detail page 2 (
row_limit=50,row_offset=50) when the connection resolves to a driver that emits ANSI ordering (e.g. PyHive). Output below is the actual compiled SQL, reproduced end to end.Before — invalid for Trino/Presto (fails with
line N: mismatched input 'OFFSET'. Expecting: <EOF>):After — valid; page 2 loads:
UI screenshots aren't included because reproducing the failure in the browser requires a live Trino/Presto cluster connected via the PyHive dialect; the SQL above is the exact statement that changes.
TESTING INSTRUCTIONS
LIMIT ... OFFSET(e.g. PyHive).mismatched input 'OFFSET'; after it, the page loads.Automated coverage added:
tests/unit_tests/db_engine_specs/test_trino.py—apply_offset_before_limitreorders ANSI ordering, leaves already-correct/limit-only SQL untouched, and is a no-op for engines that don't set the flag.tests/unit_tests/models/core_test.py— end-to-end throughcompile_sqla_query, driving a TrinoDatabasebacked by a dialect that emits ANSI ordering and asserting the compiled SQL putsOFFSETbeforeLIMIT.ADDITIONAL INFORMATION
🤖 Generated with Claude Code