STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-1495/flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch

18500 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/query.rs b/app/src/ast/query.rs
index dc5966e..d02983d 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -279,6 +279,11 @@ pub struct Select {
     pub distinct: Option<Distinct>,
     /// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
     pub top: Option<Top>,
+    /// The positioning of TOP and DISTINCT/ALL differs between dialects.
+    /// e.g. Redshift requires TOP before DISTINCT/ALL, while MSSQL uses
+    /// DISTINCT/ALL before TOP.
+    #[cfg_attr(feature = "serde", serde(default))]
+    pub top_before_distinct: bool,
     /// projection expressions
     pub projection: Vec<SelectItem>,
     /// INTO
@@ -327,11 +332,20 @@ impl fmt::Display for Select {
             write!(f, " {value_table_mode}")?;
         }
 
-        if let Some(ref distinct) = self.distinct {
-            write!(f, " {distinct}")?;
-        }
-        if let Some(ref top) = self.top {
-            write!(f, " {top}")?;
+        if self.top_before_distinct {
+            if let Some(ref top) = self.top {
+                write!(f, " {top}")?;
+            }
+            if let Some(ref distinct) = self.distinct {
+                write!(f, " {distinct}")?;
+            }
+        } else {
+            if let Some(ref distinct) = self.distinct {
+                write!(f, " {distinct}")?;
+            }
+            if let Some(ref top) = self.top {
+                write!(f, " {top}")?;
+            }
         }
         write!(f, " {}", display_comma_separated(&self.projection))?;
 
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 5abddba..e43217f 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -312,6 +312,16 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if this dialect expects `TOP` to appear before
+    /// `ALL` / `DISTINCT` in `SELECT`.
+    ///
+    /// For example:
+    /// - `SELECT TOP 10 DISTINCT ...` when true
+    /// - `SELECT DISTINCT TOP 10 ...` when false
+    fn supports_top_before_distinct(&self) -> bool {
+        false
+    }
+
     /// Dialect-specific prefix parser override
     fn parse_prefix(&self, _parser: &mut Parser) -> Option<Result<Expr, ParserError>> {
         // return None to fall back to the default behavior
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/redshift.rs b/app/src/dialect/redshift.rs
index 3bfdec3..51eca37 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/redshift.rs
+++ b/app/src/dialect/redshift.rs
@@ -68,4 +68,8 @@ impl Dialect for RedshiftSqlDialect {
     fn supports_connect_by(&self) -> bool {
         true
     }
+
+    fn supports_top_before_distinct(&self) -> bool {
+        true
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index fd7d1c5..723237b 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -9193,12 +9193,23 @@ impl<'a> Parser<'a> {
                 None
             };
 
-        let distinct = self.parse_all_or_distinct()?;
-
-        let top = if self.parse_keyword(Keyword::TOP) {
-            Some(self.parse_top()?)
+        let top_before_distinct = self.dialect.supports_top_before_distinct();
+        let (top, distinct) = if top_before_distinct {
+            let top = if self.parse_keyword(Keyword::TOP) {
+                Some(self.parse_top()?)
+            } else {
+                None
+            };
+            let distinct = self.parse_all_or_distinct()?;
+            (top, distinct)
         } else {
-            None
+            let distinct = self.parse_all_or_distinct()?;
+            let top = if self.parse_keyword(Keyword::TOP) {
+                Some(self.parse_top()?)
+            } else {
+                None
+            };
+            (top, distinct)
         };
 
         let projection = self.parse_projection()?;
@@ -9342,6 +9353,7 @@ impl<'a> Parser<'a> {
         Ok(Select {
             distinct,
             top,
+            top_before_distinct,
             projection,
             into,
             from,
@@ -11550,8 +11562,7 @@ impl<'a> Parser<'a> {
         Ok(InterpolateExpr { column, expr })
     }
 
-    /// Parse a TOP clause, MSSQL equivalent of LIMIT,
-    /// that follows after `SELECT [DISTINCT]`.
+    /// Parse a TOP clause, MSSQL equivalent of LIMIT.
     pub fn parse_top(&mut self) -> Result<Top, ParserError> {
         let quantity = if self.consume_token(&Token::LParen) {
             let quantity = self.parse_expr()?;
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_clickhouse.rs b/app/tests/sqlparser_clickhouse.rs
index f8c349a..a718711 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_clickhouse.rs
+++ b/app/tests/sqlparser_clickhouse.rs
@@ -40,6 +40,7 @@ fn parse_map_access_expr() {
         Select {
             distinct: None,
             top: None,
+            top_before_distinct: false,
             projection: vec![UnnamedExpr(MapAccess {
                 column: Box::new(Identifier(Ident {
                     value: "string_values".to_string(),
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 334dae2..36bdced 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -379,6 +379,7 @@ fn parse_update_set_from() {
                         body: Box::new(SetExpr::Select(Box::new(Select {
                             distinct: None,
                             top: None,
+                            top_before_distinct: false,
                             projection: vec![
                                 SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))),
                                 SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id"))),
@@ -4649,6 +4650,7 @@ fn test_parse_named_window() {
     let expected = Select {
         distinct: None,
         top: None,
+        top_before_distinct: false,
         projection: vec![
             SelectItem::ExprWithAlias {
                 expr: Expr::Function(Function {
@@ -5289,6 +5291,7 @@ fn parse_interval_and_or_xor() {
         body: Box::new(SetExpr::Select(Box::new(Select {
             distinct: None,
             top: None,
+            top_before_distinct: false,
             projection: vec![UnnamedExpr(Expr::Identifier(Ident {
                 value: "col".to_string(),
                 quote_style: None,
@@ -7367,6 +7370,7 @@ fn lateral_function() {
     let expected = Select {
         distinct: None,
         top: None,
+        top_before_distinct: false,
         projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
             opt_ilike: None,
             opt_exclude: None,
@@ -8215,6 +8219,7 @@ fn parse_merge() {
                         body: Box::new(SetExpr::Select(Box::new(Select {
                             distinct: None,
                             top: None,
+                            top_before_distinct: false,
                             projection: vec![SelectItem::Wildcard(
                                 WildcardAdditionalOptions::default()
                             )],
@@ -9803,6 +9808,7 @@ fn parse_unload() {
                 body: Box::new(SetExpr::Select(Box::new(Select {
                     distinct: None,
                     top: None,
+                    top_before_distinct: false,
                     projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),],
                     into: None,
                     from: vec![TableWithJoins {
@@ -9978,6 +9984,7 @@ fn parse_connect_by() {
     let expect_query = Select {
         distinct: None,
         top: None,
+        top_before_distinct: false,
         projection: vec![
             SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
             SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
@@ -10064,6 +10071,7 @@ fn parse_connect_by() {
         Select {
             distinct: None,
             top: None,
+            top_before_distinct: false,
             projection: vec![
                 SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("employee_id"))),
                 SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))),
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_duckdb.rs b/app/tests/sqlparser_duckdb.rs
index a4109b0..d68f377 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_duckdb.rs
+++ b/app/tests/sqlparser_duckdb.rs
@@ -261,6 +261,7 @@ fn test_select_union_by_name() {
             left: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
                     opt_ilike: None,
                     opt_exclude: None,
@@ -301,6 +302,7 @@ fn test_select_union_by_name() {
             right: Box::<SetExpr>::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions {
                     opt_ilike: None,
                     opt_exclude: None,
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 0223e29..6908b47 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -114,6 +114,7 @@ fn parse_create_procedure() {
                 body: Box::new(SetExpr::Select(Box::new(Select {
                     distinct: None,
                     top: None,
+                    top_before_distinct: false,
                     projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
                     into: None,
                     from: vec![],
@@ -246,6 +247,15 @@ fn parse_mssql_top() {
     let _ = ms_and_generic().one_statement_parses_to(sql, "SELECT TOP 5 bar, baz FROM foo");
 }
 
+#[test]
+fn parse_mssql_distinct_before_top() {
+    let sql = "SELECT DISTINCT TOP 5 bar FROM foo";
+    let select = ms_and_generic().verified_only_select(sql);
+    assert_eq!(select.distinct, Some(Distinct::Distinct));
+    assert!(!select.top_before_distinct);
+    let _ = ms_and_generic().one_statement_parses_to(sql, sql);
+}
+
 #[test]
 fn parse_mssql_bin_literal() {
     let _ = ms_and_generic().one_statement_parses_to("SELECT 0xdeadBEEF", "SELECT X'deadBEEF'");
@@ -514,6 +524,7 @@ fn parse_substring_in_select() {
                     body: Box::new(SetExpr::Select(Box::new(Select {
                         distinct: Some(Distinct::Distinct),
                         top: None,
+                        top_before_distinct: false,
                         projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
                             expr: Box::new(Expr::Identifier(Ident {
                                 value: "description".to_string(),
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 4b9354e..6cd08df 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -957,6 +957,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
             body: Box::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "quoted ` identifier".into(),
                     quote_style: Some('`'),
@@ -1007,6 +1008,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
             body: Box::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "quoted `` identifier".into(),
                     quote_style: Some('`'),
@@ -1050,6 +1052,7 @@ fn parse_escaped_backticks_with_escape() {
             body: Box::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "`quoted identifier`".into(),
                     quote_style: Some('`'),
@@ -1097,6 +1100,7 @@ fn parse_escaped_backticks_with_no_escape() {
             body: Box::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
                     value: "``quoted identifier``".into(),
                     quote_style: Some('`'),
@@ -1741,6 +1745,7 @@ fn parse_select_with_numeric_prefix_column_name() {
                 Box::new(SetExpr::Select(Box::new(Select {
                     distinct: None,
                     top: None,
+                    top_before_distinct: false,
                     projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(
                         "123col_$@123abc"
                     )))],
@@ -1795,6 +1800,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
                 Box::new(SetExpr::Select(Box::new(Select {
                     distinct: None,
                     top: None,
+                    top_before_distinct: false,
                     projection: vec![
                         SelectItem::UnnamedExpr(Expr::Value(number("123e4"))),
                         SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc")))
@@ -2295,6 +2301,7 @@ fn parse_substring_in_select() {
                     body: Box::new(SetExpr::Select(Box::new(Select {
                         distinct: Some(Distinct::Distinct),
                         top: None,
+                        top_before_distinct: false,
                         projection: vec![SelectItem::UnnamedExpr(Expr::Substring {
                             expr: Box::new(Expr::Identifier(Ident {
                                 value: "description".to_string(),
@@ -2616,6 +2623,7 @@ fn parse_hex_string_introducer() {
             body: Box::new(SetExpr::Select(Box::new(Select {
                 distinct: None,
                 top: None,
+                top_before_distinct: false,
                 projection: vec![SelectItem::UnnamedExpr(Expr::IntroducedString {
                     introducer: "_latin1".to_string(),
                     value: Value::HexStringLiteral("4D7953514C".to_string())
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index b9b3811..c30603b 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -1165,6 +1165,7 @@ fn parse_copy_to() {
                 body: Box::new(SetExpr::Select(Box::new(Select {
                     distinct: None,
                     top: None,
+                    top_before_distinct: false,
                     projection: vec![
                         SelectItem::ExprWithAlias {
                             expr: Expr::Value(number("42")),
@@ -2505,6 +2506,7 @@ fn parse_array_subquery_expr() {
                     left: Box::new(SetExpr::Select(Box::new(Select {
                         distinct: None,
                         top: None,
+                        top_before_distinct: false,
                         projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("1")))],
                         into: None,
                         from: vec![],
@@ -2525,6 +2527,7 @@ fn parse_array_subquery_expr() {
                     right: Box::new(SetExpr::Select(Box::new(Select {
                         distinct: None,
                         top: None,
+                        top_before_distinct: false,
                         projection: vec![SelectItem::UnnamedExpr(Expr::Value(number("2")))],
                         into: None,
                         from: vec![],
diff --git a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_redshift.rs b/app/tests/sqlparser_redshift.rs
index a25d506..2e06a76 100644
--- a/tmp/agent-patch-flux-pr-1495.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_redshift.rs
+++ b/app/tests/sqlparser_redshift.rs
@@ -196,3 +196,27 @@ fn test_create_view_with_no_schema_binding() {
     redshift_and_generic()
         .verified_stmt("CREATE VIEW myevent AS SELECT eventname FROM event WITH NO SCHEMA BINDING");
 }
+
+#[test]
+fn parse_top_before_distinct() {
+    let sql = "SELECT TOP 5 DISTINCT col1 FROM test_table";
+    let select = redshift().verified_only_select(sql);
+    assert_eq!(select.distinct, Some(Distinct::Distinct));
+    assert_eq!(
+        select.top,
+        Some(Top {
+            with_ties: false,
+            percent: false,
+            quantity: Some(TopQuantity::Constant(5)),
+        })
+    );
+    assert!(select.top_before_distinct);
+}
+
+#[test]
+fn parse_top_before_distinct_roundtrip() {
+    redshift().one_statement_parses_to(
+        "SELECT TOP 5 DISTINCT col1 FROM test_table",
+        "SELECT TOP 5 DISTINCT col1 FROM test_table",
+    );
+}