STET

agent_patch

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

17843 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/query.rs b/app/src/ast/query.rs
index 12f7293..d23d718 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -30,6 +30,51 @@ use crate::{
     tokenizer::{Token, TokenWithSpan},
 };
 
+/// Represents the LIMIT clause of a query, either using standard SQL syntax
+/// or MySQL's comma-delimited form.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum LimitClause {
+    /// Standard SQL `LIMIT <limit>` optionally followed by `OFFSET <offset>`.
+    Standard {
+        limit: Option<Expr>,
+        offset: Option<Offset>,
+    },
+    /// MySQL-style `LIMIT <offset>, <limit>` syntax.
+    CommaDelimited {
+        offset: Expr,
+        limit: Expr,
+    },
+}
+
+impl fmt::Display for LimitClause {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            LimitClause::Standard { limit, offset } => {
+                let printed_limit = if let Some(limit) = limit {
+                    write!(f, "LIMIT {limit}")?;
+                    true
+                } else {
+                    false
+                };
+                if let Some(offset) = offset {
+                    if printed_limit {
+                        write!(f, " {offset}")
+                    } else {
+                        write!(f, "{offset}")
+                    }
+                } else {
+                    Ok(())
+                }
+            }
+            LimitClause::CommaDelimited { offset, limit } => {
+                write!(f, "LIMIT {offset}, {limit}")
+            }
+        }
+    }
+}
+
 /// The most complete variant of a `SELECT` query expression, optionally
 /// including `WITH`, `UNION` / other set operations, and `ORDER BY`.
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -43,14 +88,11 @@ pub struct Query {
     pub body: Box<SetExpr>,
     /// ORDER BY
     pub order_by: Option<OrderBy>,
-    /// `LIMIT { <N> | ALL }`
-    pub limit: Option<Expr>,
+    /// `LIMIT` / `OFFSET` clause data
+    pub limit: 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 ]`
@@ -80,10 +122,7 @@ impl fmt::Display for Query {
             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}")?;
+            write!(f, " {limit}")?;
         }
         if !self.limit_by.is_empty() {
             write!(f, " BY {}", display_separated(&self.limit_by, ", "))?;
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/spans.rs b/app/src/ast/spans.rs
index fb0fc3f..d7b9ff8 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -29,13 +29,14 @@ use super::{
     Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
     FunctionArguments, GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate,
     InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
-    MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset,
-    OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition,
-    PivotValueSource, ProjectionSelect, Query, ReferentialAction, RenameSelectItem,
-    ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
-    Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
-    TableFactor, TableObject, TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use,
-    Value, Values, ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
+    LimitClause, MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName,
+    ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr,
+    OrderByKind, Partition, PivotValueSource, ProjectionSelect, Query, ReferentialAction,
+    RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
+    SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
+    TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
+    TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
+    WildcardAdditionalOptions, With, WithFill,
 };
 
 /// Given an iterator of spans, return the [Span::union] of all spans.
@@ -96,7 +97,6 @@ impl Spanned for Query {
             order_by,
             limit,
             limit_by,
-            offset,
             fetch,
             locks: _,         // todo
             for_clause: _,    // todo, mssql specific
@@ -111,12 +111,27 @@ impl Spanned for Query {
                 .chain(order_by.as_ref().map(|i| i.span()))
                 .chain(limit.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 {
+        match self {
+            LimitClause::Standard { limit, offset } => union_spans(
+                limit
+                    .iter()
+                    .map(|expr| expr.span())
+                    .chain(offset.iter().map(|offset| offset.span())),
+            ),
+            LimitClause::CommaDelimited { offset, limit } => union_spans(
+                core::iter::once(limit.span()).chain(core::iter::once(offset.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-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 32a7bcc..e460359 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -10234,7 +10234,6 @@ impl<'a> Parser<'a> {
                 limit: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10249,7 +10248,6 @@ impl<'a> Parser<'a> {
                 limit: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10264,7 +10262,6 @@ impl<'a> Parser<'a> {
                 limit: None,
                 limit_by: vec![],
                 order_by: None,
-                offset: None,
                 fetch: None,
                 locks: vec![],
                 for_clause: None,
@@ -10279,6 +10276,9 @@ impl<'a> Parser<'a> {
 
             let mut limit = None;
             let mut offset = None;
+            let mut saw_limit_comma = false;
+            let mut comma_offset_expr = None;
+            let mut comma_limit_expr = None;
 
             for _x in 0..2 {
                 if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
@@ -10296,14 +10296,33 @@ impl<'a> Parser<'a> {
                 {
                     // MySQL style LIMIT x,y => LIMIT y OFFSET x.
                     // Check <https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
+                    let offset_expr = limit.take().unwrap();
+                    let limit_expr = self.parse_expr()?;
                     offset = Some(Offset {
-                        value: limit.unwrap(),
+                        value: offset_expr.clone(),
                         rows: OffsetRows::None,
                     });
-                    limit = Some(self.parse_expr()?);
+                    comma_offset_expr = Some(offset_expr);
+                    limit = Some(limit_expr.clone());
+                    comma_limit_expr = Some(limit_expr);
+                    saw_limit_comma = true;
                 }
             }
 
+            let limit_clause = if saw_limit_comma {
+                Some(LimitClause::CommaDelimited {
+                    offset: comma_offset_expr.take().unwrap(),
+                    limit: comma_limit_expr.take().unwrap(),
+                })
+            } else if limit.is_some() || offset.is_some() {
+                Some(LimitClause::Standard {
+                    limit: limit.take(),
+                    offset: offset.take(),
+                })
+            } else {
+                None
+            };
+
             let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect)
                 && self.parse_keyword(Keyword::BY)
             {
@@ -10347,9 +10366,8 @@ impl<'a> Parser<'a> {
                 with,
                 body,
                 order_by,
-                limit,
+                limit: limit_clause,
                 limit_by,
-                offset,
                 fetch,
                 locks,
                 for_clause,
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index c7bf287..a1759cf 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -55,6 +55,25 @@ use sqlparser::ast::Expr::{Identifier, UnaryOp};
 use sqlparser::ast::Value::Number;
 use sqlparser::test_utils::all_dialects_except;
 
+fn limit_expr_from_query(query: &Query) -> Option<Expr> {
+    match &query.limit {
+        Some(LimitClause::Standard { limit, .. }) => limit.clone(),
+        Some(LimitClause::CommaDelimited { limit, .. }) => Some(limit.clone()),
+        None => None,
+    }
+}
+
+fn offset_from_query(query: &Query) -> Option<Offset> {
+    match &query.limit {
+        Some(LimitClause::Standard { offset, .. }) => offset.clone(),
+        Some(LimitClause::CommaDelimited { offset, .. }) => Some(Offset {
+            value: offset.clone(),
+            rows: OffsetRows::None,
+        }),
+        None => None,
+    }
+}
+
 #[test]
 fn parse_numeric_literal_underscore() {
     let dialects = all_dialects_where(|d| d.supports_numeric_literal_underscores());
@@ -900,7 +919,7 @@ 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(Expr::value(number("5"))), limit_expr_from_query(&select));
 }
 
 #[test]
@@ -912,10 +931,10 @@ 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(Expr::value(number("1"))), limit_expr_from_query(&ast));
 
     let ast = verified_query("SELECT 1 LIMIT 5");
-    assert_eq!(Some(Expr::value(number("5"))), ast.limit);
+    assert_eq!(Some(Expr::value(number("5"))), limit_expr_from_query(&ast));
 }
 
 #[test]
@@ -8180,21 +8199,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!(offset_from_query(&ast), expect);
     let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(offset_from_query(&ast), expect);
     let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(offset_from_query(&ast), expect);
     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!(offset_from_query(&ast), expect);
     let ast =
         dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
-    assert_eq!(ast.offset, expect);
+    assert_eq!(offset_from_query(&ast), expect);
     match *ast.body {
         SetExpr::Select(s) => match only(s.from).relation {
             TableFactor::Derived { subquery, .. } => {
-                assert_eq!(subquery.offset, expect);
+                assert_eq!(offset_from_query(subquery), expect);
             }
             _ => panic!("Test broke"),
         },
@@ -8202,7 +8221,7 @@ fn parse_offset() {
     }
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 0 ROWS");
     assert_eq!(
-        ast.offset,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::value(number("0")),
             rows: OffsetRows::Rows,
@@ -8210,7 +8229,7 @@ fn parse_offset() {
     );
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 1 ROW");
     assert_eq!(
-        ast.offset,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::value(number("1")),
             rows: OffsetRows::Row,
@@ -8218,7 +8237,7 @@ fn parse_offset() {
     );
     let ast = dialects.verified_query("SELECT 'foo' OFFSET 1");
     assert_eq!(
-        ast.offset,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::value(number("1")),
             rows: OffsetRows::None,
@@ -8274,7 +8293,7 @@ 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,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::value(number("2")),
             rows: OffsetRows::Rows,
@@ -8296,7 +8315,7 @@ 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,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::value(number("2")),
             rows: OffsetRows::Rows,
@@ -8307,7 +8326,7 @@ fn parse_fetch() {
         SetExpr::Select(s) => match only(s.from).relation {
             TableFactor::Derived { subquery, .. } => {
                 assert_eq!(
-                    subquery.offset,
+                    offset_from_query(subquery),
                     Some(Offset {
                         value: Expr::value(number("2")),
                         rows: OffsetRows::Rows,
@@ -9681,13 +9700,13 @@ fn test_placeholder() {
     let sql = "SELECT * FROM student LIMIT $1 OFFSET $2";
     let ast = dialects.verified_query(sql);
     assert_eq!(
-        ast.limit,
+        limit_expr_from_query(&ast),
         Some(Expr::Value(
             (Value::Placeholder("$1".into())).with_empty_span()
         ))
     );
     assert_eq!(
-        ast.offset,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::Value((Value::Placeholder("$2".into())).with_empty_span()),
             rows: OffsetRows::None,
@@ -9777,8 +9796,8 @@ 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!(offset_from_query(&ast), expect);
+    assert_eq!(limit_expr_from_query(&ast), Some(Expr::value(number("1"))));
 
     // different order is OK
     one_statement_parses_to("SELECT foo FROM bar OFFSET 2 LIMIT 1", sql);
@@ -9790,13 +9809,13 @@ 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");
 
     // expressions are allowed
     let sql = "SELECT foo FROM bar LIMIT 1 + 2 OFFSET 3 * 4";
     let ast = verified_query(sql);
     assert_eq!(
-        ast.limit,
+        limit_expr_from_query(&ast),
         Some(Expr::BinaryOp {
             left: Box::new(Expr::value(number("1"))),
             op: BinaryOperator::Plus,
@@ -9804,7 +9823,7 @@ fn parse_offset_and_limit() {
         }),
     );
     assert_eq!(
-        ast.offset,
+        offset_from_query(&ast),
         Some(Offset {
             value: Expr::BinaryOp {
                 left: Box::new(Expr::value(number("3"))),
diff --git a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 13a8a6c..9edb888 100644
--- a/tmp/agent-patch-flux-pr-1765.1-of-1.2026-02-28__18-27-22__gpt-5-1-codex-mini/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -2739,7 +2739,7 @@ fn parse_set_names() {
 fn parse_limit_my_sql_syntax() {
     mysql_and_generic().one_statement_parses_to(
         "SELECT id, fname, lname FROM customer LIMIT 5, 10",
-        "SELECT id, fname, lname FROM customer LIMIT 10 OFFSET 5",
+        "SELECT id, fname, lname FROM customer LIMIT 5, 10",
     );
     mysql_and_generic().verified_stmt("SELECT * FROM user LIMIT ? OFFSET ?");
 }