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
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",
+ );
+}