STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1765/flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch

38977 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/README.md b/app/README.md
index d18a76b..1a5a40d 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/README.md
+++ b/app/README.md
@@ -53,7 +53,7 @@ println!("AST: {:?}", ast);
 This outputs
 
 ```rust
-AST: [Query(Query { ctes: [], body: Select(Select { distinct: false, projection: [UnnamedExpr(Identifier("a")), UnnamedExpr(Identifier("b")), UnnamedExpr(Value(Long(123))), UnnamedExpr(Function(Function { name:ObjectName([Identifier(Ident { value: "myfunc", quote_style: None })]), args: [Identifier("b")], filter: None, over: None, distinct: false }))], from: [TableWithJoins { relation: Table { name: ObjectName([Identifier(Ident { value: "table_1", quote_style: None })]), alias: None, args: [], with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Identifier("a"), op: Gt, right: Identifier("b") }, op: And, right: BinaryOp { left: Identifier("b"), op: Lt, right: Value(Long(100)) } }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier("a"), asc: Some(false) }, OrderByExpr { expr: Identifier("b"), asc: None }], limit: None, offset: None, fetch: None })]
+AST: [Query(Query { ctes: [], body: Select(Select { distinct: false, projection: [UnnamedExpr(Identifier("a")), UnnamedExpr(Identifier("b")), UnnamedExpr(Value(Long(123))), UnnamedExpr(Function(Function { name:ObjectName([Identifier(Ident { value: "myfunc", quote_style: None })]), args: [Identifier("b")], filter: None, over: None, distinct: false }))], from: [TableWithJoins { relation: Table { name: ObjectName([Identifier(Ident { value: "table_1", quote_style: None })]), alias: None, args: [], with_hints: [] }, joins: [] }], selection: Some(BinaryOp { left: BinaryOp { left: Identifier("a"), op: Gt, right: Identifier("b") }, op: And, right: BinaryOp { left: Identifier("b"), op: Lt, right: Value(Long(100)) } }), group_by: [], having: None }), order_by: [OrderByExpr { expr: Identifier("a"), asc: Some(false) }, OrderByExpr { expr: Identifier("b"), asc: None }], limit_clause: None, fetch: None })]
 ```
 
 
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 8ab3fc0..45ea041 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -64,19 +64,20 @@ pub use self::query::{
     AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
     ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml,
     FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem,
-    InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator,
-    JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn,
-    LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
-    NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
-    OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource, ProjectionSelect, Query,
-    RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch,
-    Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr,
-    SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef,
-    TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
-    TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
-    TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
-    TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
-    ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
+    InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, Limit,
+    LimitClause, LimitSyntax, JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn,
+    JsonTableNestedColumn, LateralView, LockClause, LockType, MatchRecognizePattern,
+    MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
+    OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions,
+    PivotValueSource, ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier,
+    ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto,
+    SelectItem, SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier, Setting,
+    SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs,
+    TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample,
+    TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
+    TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
+    TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
+    WildcardAdditionalOptions, With, WithFill,
 };
 
 pub use self::trigger::{
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/query.rs b/app/src/ast/query.rs
index 12f7293..853c3fc 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -43,14 +43,11 @@ pub struct Query {
     pub body: Box<SetExpr>,
     /// ORDER BY
     pub order_by: Option<OrderBy>,
-    /// `LIMIT { <N> | ALL }`
-    pub limit: Option<Expr>,
+    /// `LIMIT`/`OFFSET` information, including whether MySQL comma syntax was used.
+    pub limit_clause: Option<LimitClause>,
 
     /// `LIMIT { <N> } BY { <expr>,<expr>,... } }`
     pub limit_by: Vec<Expr>,
-
-    /// `OFFSET <N> [ { ROW | ROWS } ]`
-    pub offset: Option<Offset>,
     /// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }`
     pub fetch: Option<Fetch>,
     /// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
@@ -79,11 +76,8 @@ impl fmt::Display for Query {
         if let Some(ref order_by) = self.order_by {
             write!(f, " {order_by}")?;
         }
-        if let Some(ref limit) = self.limit {
-            write!(f, " LIMIT {limit}")?;
-        }
-        if let Some(ref offset) = self.offset {
-            write!(f, " {offset}")?;
+        if let Some(ref limit_clause) = self.limit_clause {
+            write!(f, " {limit_clause}")?;
         }
         if !self.limit_by.is_empty() {
             write!(f, " BY {}", display_separated(&self.limit_by, ", "))?;
@@ -203,6 +197,70 @@ impl fmt::Display for SetExpr {
     }
 }
 
+/// Stores a query's `LIMIT` / `OFFSET` clause while preserving the original syntax.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct LimitClause {
+    pub limit: Option<Limit>,
+    pub offset: Option<Offset>,
+    pub syntax: LimitSyntax,
+}
+
+impl fmt::Display for LimitClause {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self.syntax {
+            LimitSyntax::Standard => {
+                if let Some(limit) = &self.limit {
+                    write!(f, "LIMIT {limit}")?;
+                }
+                if let Some(offset) = &self.offset {
+                    if self.limit.is_some() {
+                        write!(f, " {offset}")?;
+                    } else {
+                        write!(f, "{offset}")?;
+                    }
+                }
+                Ok(())
+            }
+            LimitSyntax::Comma => {
+                let offset = self.offset.as_ref().expect("comma syntax requires offset");
+                let limit = self.limit.as_ref().expect("comma syntax requires limit");
+                write!(f, "LIMIT {}, {}", offset.value, limit)
+            }
+        }
+    }
+}
+
+/// Value portion of a `LIMIT` clause.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum Limit {
+    All,
+    Quantity(Expr),
+}
+
+impl fmt::Display for Limit {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Limit::All => write!(f, "ALL"),
+            Limit::Quantity(expr) => write!(f, "{expr}"),
+        }
+    }
+}
+
+/// Syntax used for a query `LIMIT` clause.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum LimitSyntax {
+    /// `LIMIT <limit> [OFFSET <offset>]` and `OFFSET <offset> [LIMIT <limit>]`
+    Standard,
+    /// MySQL-style `LIMIT <offset>, <limit>`
+    Comma,
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs b/app/src/ast/spans.rs
index fb0fc3f..fd985b6 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -94,9 +94,8 @@ impl Spanned for Query {
             with,
             body,
             order_by,
-            limit,
+            limit_clause,
             limit_by,
-            offset,
             fetch,
             locks: _,         // todo
             for_clause: _,    // todo, mssql specific
@@ -109,14 +108,35 @@ impl Spanned for Query {
                 .map(|i| i.span())
                 .chain(core::iter::once(body.span()))
                 .chain(order_by.as_ref().map(|i| i.span()))
-                .chain(limit.as_ref().map(|i| i.span()))
+                .chain(limit_clause.as_ref().map(|i| i.span()))
                 .chain(limit_by.iter().map(|i| i.span()))
-                .chain(offset.as_ref().map(|i| i.span()))
                 .chain(fetch.as_ref().map(|i| i.span())),
         )
     }
 }
 
+impl Spanned for LimitClause {
+    fn span(&self) -> Span {
+        let LimitClause { limit, offset, syntax: _ } = self;
+
+        union_spans(
+            limit
+                .iter()
+                .map(|i| i.span())
+                .chain(offset.as_ref().map(|i| i.span())),
+        )
+    }
+}
+
+impl Spanned for Limit {
+    fn span(&self) -> Span {
+        match self {
+            Limit::All => Span::empty(),
+            Limit::Quantity(expr) => expr.span(),
+        }
+    }
+}
+
 impl Spanned for Offset {
     fn span(&self) -> Span {
         let Offset {
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/visitor.rs b/app/src/ast/visitor.rs
index a5d355f..5581c8b 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/visitor.rs
+++ b/app/src/ast/visitor.rs
@@ -523,7 +523,7 @@ where
 /// // Remove all select limits in sub-queries
 /// visit_expressions_mut(&mut statements, |expr| {
 ///   if let Expr::Subquery(q) = expr {
-///      q.limit = None
+///      q.limit_clause = None
 ///   }
 ///   ControlFlow::<()>::Continue(())
 /// });
@@ -647,7 +647,7 @@ where
 /// // Remove all select limits in outer statements (not in sub-queries)
 /// visit_statements_mut(&mut statements, |stmt| {
 ///   if let Statement::Query(q) = stmt {
-///      q.limit = None
+///      q.limit_clause = None
 ///   }
 ///   ControlFlow::<()>::Continue(())
 /// });
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 8d4557e..014f670 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -443,7 +443,7 @@ pub trait Dialect: Debug + Any {
         false
     }
 
-    /// Does the dialect support parsing `LIMIT 1, 2` as `LIMIT 2 OFFSET 1`?
+    /// Does the dialect support parsing MySQL-style `LIMIT <offset>, <limit>`?
     fn supports_limit_comma(&self) -> bool {
         false
     }
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 32a7bcc..2af92e0 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -10095,7 +10095,7 @@ impl<'a> Parser<'a> {
             vec![]
         };
         let limit = if self.parse_keyword(Keyword::LIMIT) {
-            self.parse_limit()?
+            self.parse_limit_expr()?
         } else {
             None
         };
@@ -10231,10 +10231,9 @@ impl<'a> Parser<'a> {
             Ok(Query {
                 with,
                 body: self.parse_insert_setexpr_boxed()?,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10246,10 +10245,9 @@ impl<'a> Parser<'a> {
             Ok(Query {
                 with,
                 body: self.parse_update_setexpr_boxed()?,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10261,10 +10259,9 @@ impl<'a> Parser<'a> {
             Ok(Query {
                 with,
                 body: self.parse_delete_setexpr_boxed()?,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10279,6 +10276,7 @@ impl<'a> Parser<'a> {
 
             let mut limit = None;
             let mut offset = None;
+            let mut syntax = LimitSyntax::Standard;
 
             for _x in 0..2 {
                 if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
@@ -10294,16 +10292,32 @@ impl<'a> Parser<'a> {
                     && offset.is_none()
                     && self.consume_token(&Token::Comma)
                 {
-                    // MySQL style LIMIT x,y => LIMIT y OFFSET x.
+                    // MySQL style LIMIT x,y.
                     // Check <https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
                     offset = Some(Offset {
-                        value: limit.unwrap(),
+                        value: match limit.take().expect("limit was checked as present") {
+                            Limit::Quantity(expr) => expr,
+                            Limit::All => {
+                                return self.expected("expression after LIMIT", self.peek_token())
+                            }
+                        },
                         rows: OffsetRows::None,
                     });
-                    limit = Some(self.parse_expr()?);
+                    limit = Some(Limit::Quantity(self.parse_expr()?));
+                    syntax = LimitSyntax::Comma;
                 }
             }
 
+            let limit_clause = if limit.is_some() || offset.is_some() {
+                Some(LimitClause {
+                    limit,
+                    offset,
+                    syntax,
+                })
+            } else {
+                None
+            };
+
             let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect)
                 && self.parse_keyword(Keyword::BY)
             {
@@ -10347,9 +10361,8 @@ impl<'a> Parser<'a> {
                 with,
                 body,
                 order_by,
-                limit,
+                limit_clause,
                 limit_by,
-                offset,
                 fetch,
                 locks,
                 for_clause,
@@ -11809,9 +11822,8 @@ impl<'a> Parser<'a> {
                     with: None,
                     body: Box::new(values),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -13940,15 +13952,23 @@ impl<'a> Parser<'a> {
         })
     }
 
-    /// Parse a LIMIT clause
-    pub fn parse_limit(&mut self) -> Result<Option<Expr>, ParserError> {
+    /// Parse a query LIMIT clause
+    pub fn parse_limit(&mut self) -> Result<Option<Limit>, ParserError> {
         if self.parse_keyword(Keyword::ALL) {
-            Ok(None)
+            Ok(Some(Limit::All))
         } else {
-            Ok(Some(self.parse_expr()?))
+            Ok(Some(Limit::Quantity(self.parse_expr()?)))
         }
     }
 
+    /// Parse a LIMIT clause where only an expression is meaningful.
+    pub fn parse_limit_expr(&mut self) -> Result<Option<Expr>, ParserError> {
+        Ok(match self.parse_limit()? {
+            Some(Limit::Quantity(expr)) => Some(expr),
+            Some(Limit::All) | None => None,
+        })
+    }
+
     /// Parse an OFFSET clause
     pub fn parse_offset(&mut self) -> Result<Offset, ParserError> {
         let value = self.parse_expr()?;
@@ -14891,7 +14911,7 @@ impl<'a> Parser<'a> {
 
     fn maybe_parse_show_stmt_limit(&mut self) -> Result<Option<Expr>, ParserError> {
         if self.parse_keyword(Keyword::LIMIT) {
-            Ok(self.parse_limit()?)
+            self.parse_limit_expr()
         } else {
             Ok(None)
         }
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index c7bf287..0071375 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -483,9 +483,8 @@ fn parse_update_set_from() {
                             flavor: SelectFlavor::Standard,
                         }))),
                         order_by: None,
-                        limit: None,
+                        limit_clause: None,
                         limit_by: vec![],
-                        offset: None,
                         fetch: None,
                         locks: vec![],
                         for_clause: None,
@@ -900,7 +899,10 @@ fn parse_simple_select() {
     assert!(select.distinct.is_none());
     assert_eq!(3, select.projection.len());
     let select = verified_query(sql);
-    assert_eq!(Some(Expr::value(number("5"))), select.limit);
+    assert_eq!(
+        Some(&Limit::Quantity(Expr::value(number("5")))),
+        query_limit(&select)
+    );
 }
 
 #[test]
@@ -912,10 +914,16 @@ fn parse_limit() {
 fn parse_limit_is_not_an_alias() {
     // In dialects supporting LIMIT it shouldn't be parsed as a table alias
     let ast = verified_query("SELECT id FROM customer LIMIT 1");
-    assert_eq!(Some(Expr::value(number("1"))), ast.limit);
+    assert_eq!(
+        Some(&Limit::Quantity(Expr::value(number("1")))),
+        query_limit(&ast)
+    );
 
     let ast = verified_query("SELECT 1 LIMIT 5");
-    assert_eq!(Some(Expr::value(number("5"))), ast.limit);
+    assert_eq!(
+        Some(&Limit::Quantity(Expr::value(number("5")))),
+        query_limit(&ast)
+    );
 }
 
 #[test]
@@ -2862,7 +2870,7 @@ fn parse_select_qualify() {
 fn parse_limit_accepts_all() {
     one_statement_parses_to(
         "SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT ALL",
-        "SELECT id, fname, lname FROM customer WHERE id = 1",
+        "SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT ALL",
     );
 }
 
@@ -4247,9 +4255,8 @@ fn parse_create_table_as_table() {
             schema_name: None,
         }))),
         order_by: None,
-        limit: None,
+        limit_clause: None,
         limit_by: vec![],
-        offset: None,
         fetch: None,
         locks: vec![],
         for_clause: None,
@@ -4274,9 +4281,8 @@ fn parse_create_table_as_table() {
             schema_name: Some("schema_name".to_string()),
         }))),
         order_by: None,
-        limit: None,
+        limit_clause: None,
         limit_by: vec![],
-        offset: None,
         fetch: None,
         locks: vec![],
         for_clause: None,
@@ -6273,9 +6279,8 @@ fn parse_interval_and_or_xor() {
             flavor: SelectFlavor::Standard,
         }))),
         order_by: None,
-        limit: None,
+        limit_clause: None,
         limit_by: vec![],
-        offset: None,
         fetch: None,
         locks: vec![],
         for_clause: None,
@@ -8180,21 +8185,21 @@ fn parse_offset() {
         rows: OffsetRows::Rows,
     });
     let ast = dialects.verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(query_offset(&ast), expect.as_ref());
     let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(query_offset(&ast), expect.as_ref());
     let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(query_offset(&ast), expect.as_ref());
     let ast =
         dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(query_offset(&ast), expect.as_ref());
     let ast =
         dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(query_offset(&ast), expect.as_ref());
     match *ast.body {
         SetExpr::Select(s) => match only(s.from).relation {
             TableFactor::Derived { subquery, .. } => {
-                assert_eq!(subquery.offset, expect);
+                assert_eq!(query_offset(&subquery), expect.as_ref());
             }
             _ => panic!("Test broke"),
         },
@@ -8202,24 +8207,24 @@ fn parse_offset() {
     }
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 0 ROWS");
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::value(number("0")),
             rows: OffsetRows::Rows,
         })
     );
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 1 ROW");
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::value(number("1")),
             rows: OffsetRows::Row,
         })
     );
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 1");
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::value(number("1")),
             rows: OffsetRows::None,
         })
@@ -8274,8 +8279,8 @@ fn parse_fetch() {
         "SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY",
     );
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::value(number("2")),
             rows: OffsetRows::Rows,
         })
@@ -8296,8 +8301,8 @@ fn parse_fetch() {
     }
     let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY");
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::value(number("2")),
             rows: OffsetRows::Rows,
         })
@@ -8307,8 +8312,8 @@ fn parse_fetch() {
         SetExpr::Select(s) => match only(s.from).relation {
             TableFactor::Derived { subquery, .. } => {
                 assert_eq!(
-                    subquery.offset,
-                    Some(Offset {
+                    query_offset(&subquery),
+                    Some(&Offset {
                         value: Expr::value(number("2")),
                         rows: OffsetRows::Rows,
                     })
@@ -9358,9 +9363,8 @@ fn parse_merge() {
                             flavor: SelectFlavor::Standard,
                         }))),
                         order_by: None,
-                        limit: None,
+                        limit_clause: None,
                         limit_by: vec![],
-                        offset: None,
                         fetch: None,
                         locks: vec![],
                         for_clause: None,
@@ -9681,14 +9685,14 @@ fn test_placeholder() {
     let sql = "SELECT * FROM student LIMIT $1 OFFSET $2";
     let ast = dialects.verified_query(sql);
     assert_eq!(
-        ast.limit,
-        Some(Expr::Value(
+        query_limit(&ast),
+        Some(&Limit::Quantity(Expr::Value(
             (Value::Placeholder("$1".into())).with_empty_span()
-        ))
+        )))
     );
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::Value((Value::Placeholder("$2".into())).with_empty_span()),
             rows: OffsetRows::None,
         }),
@@ -9761,6 +9765,20 @@ fn verified_query(query: &str) -> Query {
     all_dialects().verified_query(query)
 }
 
+fn query_limit(query: &Query) -> Option<&Limit> {
+    query
+        .limit_clause
+        .as_ref()
+        .and_then(|limit_clause| limit_clause.limit.as_ref())
+}
+
+fn query_offset(query: &Query) -> Option<&Offset> {
+    query
+        .limit_clause
+        .as_ref()
+        .and_then(|limit_clause| limit_clause.offset.as_ref())
+}
+
 fn verified_only_select(query: &str) -> Select {
     all_dialects().verified_only_select(query)
 }
@@ -9777,8 +9795,11 @@ fn parse_offset_and_limit() {
         rows: OffsetRows::None,
     });
     let ast = verified_query(sql);
-    assert_eq!(ast.offset, expect);
-    assert_eq!(ast.limit, Some(Expr::value(number("1"))));
+    assert_eq!(query_offset(&ast), expect.as_ref());
+    assert_eq!(
+        query_limit(&ast),
+        Some(&Limit::Quantity(Expr::value(number("1"))))
+    );
 
     // different order is OK
     one_statement_parses_to("SELECT foo FROM bar OFFSET 2 LIMIT 1", sql);
@@ -9790,22 +9811,28 @@ fn parse_offset_and_limit() {
         Box::new(SQLiteDialect {}),
         Box::new(ClickHouseDialect {}),
     ])
-    .one_statement_parses_to("SELECT foo FROM bar LIMIT 2, 1", sql);
+    .one_statement_parses_to("SELECT foo FROM bar LIMIT 2, 1", "SELECT foo FROM bar LIMIT 2, 1");
+    let ast = TestedDialects::new(vec![Box::new(MySqlDialect {})])
+        .verified_query("SELECT foo FROM bar LIMIT 2, 1");
+    assert_eq!(
+        ast.limit_clause.as_ref().map(|limit_clause| &limit_clause.syntax),
+        Some(&LimitSyntax::Comma)
+    );
 
     // expressions are allowed
     let sql = "SELECT foo FROM bar LIMIT 1 + 2 OFFSET 3 * 4";
     let ast = verified_query(sql);
     assert_eq!(
-        ast.limit,
-        Some(Expr::BinaryOp {
+        query_limit(&ast),
+        Some(&Limit::Quantity(Expr::BinaryOp {
             left: Box::new(Expr::value(number("1"))),
             op: BinaryOperator::Plus,
             right: Box::new(Expr::value(number("2"))),
-        }),
+        })),
     );
     assert_eq!(
-        ast.offset,
-        Some(Offset {
+        query_offset(&ast),
+        Some(&Offset {
             value: Expr::BinaryOp {
                 left: Box::new(Expr::value(number("3"))),
                 op: BinaryOperator::Multiply,
@@ -11227,9 +11254,8 @@ fn parse_unload() {
                     flavor: SelectFlavor::Standard,
                 }))),
                 with: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -12400,9 +12426,8 @@ fn test_extract_seconds_ok() {
             flavor: SelectFlavor::Standard,
         }))),
         order_by: None,
-        limit: None,
+        limit_clause: None,
         limit_by: vec![],
-        offset: None,
         fetch: None,
         locks: vec![],
         for_clause: None,
@@ -14265,8 +14290,7 @@ fn test_select_from_first() {
                 flavor,
             }))),
             order_by: None,
-            limit: None,
-            offset: None,
+            limit_clause: None,
             fetch: None,
             locks: vec![],
             limit_by: vec![],
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 386bd17..645af35 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -107,9 +107,8 @@ fn parse_create_procedure() {
             or_alter: true,
             body: vec![Statement::Query(Box::new(Query {
                 with: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -1161,9 +1160,8 @@ fn parse_substring_in_select() {
                         flavor: SelectFlavor::Standard,
                     }))),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1264,9 +1262,8 @@ fn parse_mssql_declare() {
             }),
             Statement::Query(Box::new(Query {
                 with: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 13a8a6c..6791b12 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -1107,9 +1107,8 @@ fn parse_escaped_quote_identifiers_with_escape() {
                 flavor: SelectFlavor::Standard,
             }))),
             order_by: None,
-            limit: None,
+            limit_clause: None,
             limit_by: vec![],
-            offset: None,
             fetch: None,
             locks: vec![],
             for_clause: None,
@@ -1161,9 +1160,8 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
                 flavor: SelectFlavor::Standard,
             }))),
             order_by: None,
-            limit: None,
+            limit_clause: None,
             limit_by: vec![],
-            offset: None,
             fetch: None,
             locks: vec![],
             for_clause: None,
@@ -1209,9 +1207,8 @@ fn parse_escaped_backticks_with_escape() {
                 flavor: SelectFlavor::Standard,
             }))),
             order_by: None,
-            limit: None,
+            limit_clause: None,
             limit_by: vec![],
-            offset: None,
             fetch: None,
             locks: vec![],
             for_clause: None,
@@ -1261,9 +1258,8 @@ fn parse_escaped_backticks_with_no_escape() {
                 flavor: SelectFlavor::Standard,
             }))),
             order_by: None,
-            limit: None,
+            limit_clause: None,
             limit_by: vec![],
-            offset: None,
             fetch: None,
             locks: vec![],
             for_clause: None,
@@ -1438,9 +1434,8 @@ fn parse_simple_insert() {
                         ]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1488,9 +1483,8 @@ fn parse_ignore_insert() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1538,9 +1532,8 @@ fn parse_priority_insert() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1585,9 +1578,8 @@ fn parse_priority_insert() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1634,9 +1626,8 @@ fn parse_insert_as() {
                         )]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1698,9 +1689,8 @@ fn parse_insert_as() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1749,9 +1739,8 @@ fn parse_replace_insert() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1791,9 +1780,8 @@ fn parse_empty_row_insert() {
                         rows: vec![vec![], vec![]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -1857,9 +1845,8 @@ fn parse_insert_with_on_duplicate_update() {
                         ]]
                     })),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -2596,9 +2583,8 @@ fn parse_substring_in_select() {
                         flavor: SelectFlavor::Standard,
                     }))),
                     order_by: None,
-                    limit: None,
+                    limit_clause: None,
                     limit_by: vec![],
-                    offset: None,
                     fetch: None,
                     locks: vec![],
                     for_clause: None,
@@ -2903,10 +2889,9 @@ fn parse_hex_string_introducer() {
                 flavor: SelectFlavor::Standard,
             }))),
             order_by: None,
-            limit: None,
-            limit_by: vec![],
-            offset: None,
-            fetch: None,
+                    limit_clause: None,
+                    limit_by: vec![],
+                    fetch: None,
             locks: vec![],
             for_clause: None,
             settings: None,
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index a65c4fa..173b299 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -1319,9 +1319,8 @@ fn parse_copy_to() {
                     flavor: SelectFlavor::Standard,
                 }))),
                 order_by: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -2955,9 +2954,8 @@ fn parse_array_subquery_expr() {
                     }))),
                 }),
                 order_by: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -4747,9 +4745,8 @@ fn test_simple_postgres_insert_with_alias() {
                     ]]
                 })),
                 order_by: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -4820,9 +4817,8 @@ fn test_simple_postgres_insert_with_alias() {
                     ]]
                 })),
                 order_by: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -4891,9 +4887,8 @@ fn test_simple_insert_with_quoted_alias() {
                     ]]
                 })),
                 order_by: None,
-                limit: None,
+                limit_clause: None,
                 limit_by: vec![],
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,