agent_patch
runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1791/flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch
12530 bytes
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index a6dc682..4509daa 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -92,6 +92,7 @@ pub use self::value::{
use crate::ast::helpers::key_value_options::KeyValueOptions;
use crate::ast::helpers::stmt_data_loading::{StageLoadSelectItem, StageParamsObject};
+use crate::keywords::Keyword;
#[cfg(feature = "visitor")]
pub use visitor::*;
@@ -2121,8 +2122,8 @@ pub struct CaseStatement {
pub match_expr: Option<Expr>,
pub when_blocks: Vec<ConditionalStatements>,
pub else_block: Option<Vec<Statement>>,
- /// TRUE if the statement ends with `END CASE` (vs `END`).
- pub has_end_case: bool,
+ /// Optional keyword after `END`, such as `CASE`.
+ pub end_token: Option<Keyword>,
}
impl fmt::Display for CaseStatement {
@@ -2131,7 +2132,7 @@ impl fmt::Display for CaseStatement {
match_expr,
when_blocks,
else_block,
- has_end_case,
+ end_token,
} = self;
write!(f, "CASE")?;
@@ -2150,8 +2151,8 @@ impl fmt::Display for CaseStatement {
}
write!(f, " END")?;
- if *has_end_case {
- write!(f, " CASE")?;
+ if let Some(end_token) = end_token {
+ write!(f, " {end_token}")?;
}
Ok(())
@@ -2181,6 +2182,8 @@ pub struct IfStatement {
pub if_block: ConditionalStatements,
pub elseif_blocks: Vec<ConditionalStatements>,
pub else_block: Option<Vec<Statement>>,
+ /// Optional keyword after `END`, such as `IF`.
+ pub end_token: Option<Keyword>,
}
impl fmt::Display for IfStatement {
@@ -2189,6 +2192,7 @@ impl fmt::Display for IfStatement {
if_block,
elseif_blocks,
else_block,
+ end_token,
} = self;
write!(f, "{if_block}")?;
@@ -2202,7 +2206,9 @@ impl fmt::Display for IfStatement {
format_statement_list(f, else_block)?;
}
- write!(f, " END IF")?;
+ if let Some(end_token) = end_token {
+ write!(f, " END {end_token}")?;
+ }
Ok(())
}
@@ -2234,18 +2240,21 @@ pub enum ConditionalStatementKind {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct ConditionalStatements {
/// The condition expression.
- pub condition: Expr,
- /// Statement list of the `THEN` clause.
+ pub condition: Option<Expr>,
+ /// Statement list of the conditional body.
pub statements: Vec<Statement>,
pub kind: ConditionalStatementKind,
+ /// TRUE if the conditional body is introduced by `THEN`.
+ pub has_then_keyword: bool,
}
impl fmt::Display for ConditionalStatements {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ConditionalStatements {
- condition: expr,
+ condition,
statements,
kind,
+ has_then_keyword,
} = self;
let kind = match kind {
@@ -2254,7 +2263,15 @@ impl fmt::Display for ConditionalStatements {
ConditionalStatementKind::ElseIf => "ELSEIF",
};
- write!(f, "{kind} {expr} THEN")?;
+ write!(f, "{kind}")?;
+
+ if let Some(expr) = condition {
+ write!(f, " {expr}")?;
+ }
+
+ if *has_then_keyword {
+ write!(f, " THEN")?;
+ }
if !statements.is_empty() {
write!(f, " ")?;
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 11770d1..986a1f2 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -742,7 +742,7 @@ impl Spanned for CaseStatement {
match_expr,
when_blocks,
else_block,
- has_end_case: _,
+ end_token: _,
} = self;
union_spans(
@@ -761,6 +761,7 @@ impl Spanned for IfStatement {
if_block,
elseif_blocks,
else_block,
+ end_token: _,
} = self;
union_spans(
@@ -777,9 +778,15 @@ impl Spanned for ConditionalStatements {
condition,
statements,
kind: _,
+ has_then_keyword: _,
} = self;
- union_spans(iter::once(condition.span()).chain(statements.iter().map(|s| s.span())))
+ union_spans(
+ condition
+ .iter()
+ .map(|expr| expr.span())
+ .chain(statements.iter().map(|s| s.span())),
+ )
}
}
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 5495499..0ef7d50 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -654,13 +654,13 @@ impl<'a> Parser<'a> {
};
self.expect_keyword_is(Keyword::END)?;
- let has_end_case = self.parse_keyword(Keyword::CASE);
+ let end_token = self.parse_one_of_keywords(&[Keyword::CASE]);
Ok(Statement::Case(CaseStatement {
match_expr,
when_blocks,
else_block,
- has_end_case,
+ end_token,
}))
}
@@ -669,6 +669,11 @@ impl<'a> Parser<'a> {
/// See [Statement::If]
pub fn parse_if_stmt(&mut self) -> Result<Statement, ParserError> {
self.expect_keyword_is(Keyword::IF)?;
+
+ if dialect_of!(self is MsSqlDialect) {
+ return self.parse_mssql_if_stmt();
+ }
+
let if_block = self.parse_conditional_statements(
ConditionalStatementKind::If,
&[Keyword::ELSE, Keyword::ELSEIF, Keyword::END],
@@ -697,6 +702,7 @@ impl<'a> Parser<'a> {
if_block,
elseif_blocks,
else_block,
+ end_token: Some(Keyword::IF),
}))
}
@@ -712,7 +718,7 @@ impl<'a> Parser<'a> {
kind: ConditionalStatementKind,
terminal_keywords: &[Keyword],
) -> Result<ConditionalStatements, ParserError> {
- let condition = self.parse_expr()?;
+ let condition = Some(self.parse_expr()?);
self.expect_keyword_is(Keyword::THEN)?;
let statements = self.parse_statement_list(terminal_keywords)?;
@@ -720,6 +726,79 @@ impl<'a> Parser<'a> {
condition,
statements,
kind,
+ has_then_keyword: true,
+ })
+ }
+
+ fn parse_mssql_if_stmt(&mut self) -> Result<Statement, ParserError> {
+ let if_block = self.parse_mssql_conditional_statements(ConditionalStatementKind::If)?;
+
+ let else_block = if self.parse_keyword(Keyword::ELSE) {
+ Some(self.parse_mssql_conditional_body()?)
+ } else {
+ None
+ };
+
+ Ok(Statement::If(IfStatement {
+ if_block,
+ elseif_blocks: vec![],
+ else_block,
+ end_token: None,
+ }))
+ }
+
+ fn parse_mssql_conditional_statements(
+ &mut self,
+ kind: ConditionalStatementKind,
+ ) -> Result<ConditionalStatements, ParserError> {
+ let condition = Some(self.parse_expr()?);
+ let statements = self.parse_mssql_conditional_body()?;
+
+ Ok(ConditionalStatements {
+ condition,
+ statements,
+ kind,
+ has_then_keyword: false,
+ })
+ }
+
+ fn parse_mssql_conditional_body(&mut self) -> Result<Vec<Statement>, ParserError> {
+ if self.peek_keyword(Keyword::BEGIN)
+ && !matches!(
+ self.peek_nth_token_ref(1).token,
+ Token::Word(ref w)
+ if matches!(
+ w.keyword,
+ Keyword::TRANSACTION
+ | Keyword::WORK
+ | Keyword::TRY
+ | Keyword::CATCH
+ | Keyword::DEFERRED
+ | Keyword::IMMEDIATE
+ | Keyword::EXCLUSIVE
+ )
+ )
+ {
+ return Ok(vec![self.parse_begin_end_block()?]);
+ }
+
+ let statement = self.parse_statement()?;
+ self.consume_token(&Token::SemiColon);
+ Ok(vec![statement])
+ }
+
+ fn parse_begin_end_block(&mut self) -> Result<Statement, ParserError> {
+ self.expect_keyword_is(Keyword::BEGIN)?;
+ let statements = self.parse_statements()?;
+ self.expect_keyword_is(Keyword::END)?;
+ Ok(Statement::StartTransaction {
+ modes: vec![],
+ begin: true,
+ transaction: None,
+ modifier: None,
+ statements,
+ exception_statements: None,
+ has_end_keyword: true,
})
}
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 3653321..ec7a646 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -14229,9 +14229,11 @@ fn parse_case_statement() {
};
assert_eq!(Some(Expr::value(number("1"))), stmt.match_expr);
- assert_eq!(Expr::value(number("2")), stmt.when_blocks[0].condition);
+ assert_eq!(Some(Expr::value(number("2"))), stmt.when_blocks[0].condition);
assert_eq!(2, stmt.when_blocks[0].statements.len());
+ assert!(stmt.when_blocks[0].has_then_keyword);
assert_eq!(1, stmt.else_block.unwrap().len());
+ assert_eq!(Some(Keyword::CASE), stmt.end_token);
verified_stmt(concat!(
"CASE 1",
@@ -14280,9 +14282,11 @@ fn parse_if_statement() {
let Statement::If(stmt) = verified_stmt(sql) else {
unreachable!()
};
- assert_eq!(Expr::value(number("1")), stmt.if_block.condition);
- assert_eq!(Expr::value(number("2")), stmt.elseif_blocks[0].condition);
+ assert_eq!(Some(Expr::value(number("1"))), stmt.if_block.condition);
+ assert_eq!(Some(Expr::value(number("2"))), stmt.elseif_blocks[0].condition);
+ assert!(stmt.if_block.has_then_keyword);
assert_eq!(1, stmt.else_block.unwrap().len());
+ assert_eq!(Some(Keyword::IF), stmt.end_token);
verified_stmt(concat!(
"IF 1 THEN",
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 5d76fd0..5a5ab89 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -187,6 +187,52 @@ fn parse_mssql_create_procedure() {
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END");
}
+#[test]
+fn parse_mssql_if_statement_single_statement_bodies() {
+ let sql = "IF 1 SELECT 1 ELSE SELECT 2";
+ let Statement::If(stmt) = ms().verified_stmt(sql) else {
+ unreachable!()
+ };
+
+ assert_eq!(Some(Expr::value(number("1"))), stmt.if_block.condition);
+ assert!(!stmt.if_block.has_then_keyword);
+ assert_eq!(1, stmt.if_block.statements.len());
+ assert_eq!(1, stmt.else_block.unwrap().len());
+ assert_eq!(None, stmt.end_token);
+
+ ms().one_statement_parses_to(sql, sql);
+}
+
+#[test]
+fn parse_mssql_if_statement_begin_end_blocks() {
+ let sql = "IF 1 BEGIN SELECT 1; SELECT 2 END ELSE BEGIN SELECT 3 END";
+ let Statement::If(stmt) = ms().verified_stmt(sql) else {
+ unreachable!()
+ };
+
+ assert_eq!(1, stmt.if_block.statements.len());
+ assert!(matches!(
+ &stmt.if_block.statements[0],
+ Statement::StartTransaction {
+ begin: true,
+ has_end_keyword: true,
+ statements,
+ ..
+ } if statements.len() == 2
+ ));
+ assert!(matches!(
+ &stmt.else_block.as_ref().unwrap()[0],
+ Statement::StartTransaction {
+ begin: true,
+ has_end_keyword: true,
+ statements,
+ ..
+ } if statements.len() == 1
+ ));
+
+ ms().one_statement_parses_to(sql, sql);
+}
+
#[test]
fn parse_mssql_apply_join() {
let _ = ms_and_generic().verified_only_select(