STET

agent_patch

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

13852 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs b/app/src/ast/mod.rs
index a6dc682..a0dec63 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -2121,8 +2121,10 @@ 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 `END` token.
+    pub end_token: Option<AttachedToken>,
+    /// Optional `CASE` token after `END`.
+    pub case_token: Option<AttachedToken>,
 }
 
 impl fmt::Display for CaseStatement {
@@ -2131,7 +2133,8 @@ impl fmt::Display for CaseStatement {
             match_expr,
             when_blocks,
             else_block,
-            has_end_case,
+            end_token,
+            case_token,
         } = self;
 
         write!(f, "CASE")?;
@@ -2149,8 +2152,10 @@ impl fmt::Display for CaseStatement {
             format_statement_list(f, else_block)?;
         }
 
-        write!(f, " END")?;
-        if *has_end_case {
+        if end_token.is_some() {
+            write!(f, " END")?;
+        }
+        if case_token.is_some() {
             write!(f, " CASE")?;
         }
 
@@ -2181,6 +2186,10 @@ pub struct IfStatement {
     pub if_block: ConditionalStatements,
     pub elseif_blocks: Vec<ConditionalStatements>,
     pub else_block: Option<Vec<Statement>>,
+    /// Optional `END` token.
+    pub end_token: Option<AttachedToken>,
+    /// Optional `IF` token after `END`.
+    pub if_token: Option<AttachedToken>,
 }
 
 impl fmt::Display for IfStatement {
@@ -2189,6 +2198,8 @@ impl fmt::Display for IfStatement {
             if_block,
             elseif_blocks,
             else_block,
+            end_token,
+            if_token,
         } = self;
 
         write!(f, "{if_block}")?;
@@ -2202,7 +2213,12 @@ impl fmt::Display for IfStatement {
             format_statement_list(f, else_block)?;
         }
 
-        write!(f, " END IF")?;
+        if end_token.is_some() {
+            write!(f, " END")?;
+            if if_token.is_some() {
+                write!(f, " IF")?;
+            }
+        }
 
         Ok(())
     }
@@ -2234,9 +2250,11 @@ 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 clause body.
     pub statements: Vec<Statement>,
+    /// Optional `THEN` token.
+    pub then_token: Option<AttachedToken>,
     pub kind: ConditionalStatementKind,
 }
 
@@ -2245,6 +2263,7 @@ impl fmt::Display for ConditionalStatements {
         let ConditionalStatements {
             condition: expr,
             statements,
+            then_token,
             kind,
         } = self;
 
@@ -2254,7 +2273,13 @@ impl fmt::Display for ConditionalStatements {
             ConditionalStatementKind::ElseIf => "ELSEIF",
         };
 
-        write!(f, "{kind} {expr} THEN")?;
+        write!(f, "{kind}")?;
+        if let Some(expr) = expr {
+            write!(f, " {expr}")?;
+        }
+        if then_token.is_some() {
+            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-3-codex/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 11770d1..d0587a9 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -742,7 +742,8 @@ impl Spanned for CaseStatement {
             match_expr,
             when_blocks,
             else_block,
-            has_end_case: _,
+            end_token: _,
+            case_token: _,
         } = self;
 
         union_spans(
@@ -761,6 +762,8 @@ impl Spanned for IfStatement {
             if_block,
             elseif_blocks,
             else_block,
+            end_token: _,
+            if_token: _,
         } = self;
 
         union_spans(
@@ -776,10 +779,16 @@ impl Spanned for ConditionalStatements {
         let ConditionalStatements {
             condition,
             statements,
+            then_token: _,
             kind: _,
         } = self;
 
-        union_spans(iter::once(condition.span()).chain(statements.iter().map(|s| s.span())))
+        union_spans(
+            condition
+                .iter()
+                .map(|c| c.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-3-codex/app/src/dialect/mssql.rs b/app/src/dialect/mssql.rs
index 18a963a..8bdc384 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/dialect/mssql.rs
+++ b/app/src/dialect/mssql.rs
@@ -15,13 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::ast::Statement;
 use crate::dialect::Dialect;
+use crate::keywords::Keyword;
+use crate::parser::{Parser, ParserError};
 
 /// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/)
 #[derive(Debug)]
 pub struct MsSqlDialect {}
 
 impl Dialect for MsSqlDialect {
+    fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
+        if parser.peek_keyword(Keyword::IF) {
+            return Some(parser.parse_mssql_if_stmt());
+        }
+        None
+    }
+
     fn is_delimited_identifier_start(&self, ch: char) -> bool {
         ch == '"' || ch == '['
     }
diff --git a/tmp/agent-patch-flux-pr-1791.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 5495499..1401175 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -644,6 +644,7 @@ impl<'a> Parser<'a> {
             parser.parse_conditional_statements(
                 ConditionalStatementKind::When,
                 &[Keyword::WHEN, Keyword::ELSE, Keyword::END],
+                true,
             )
         })?;
 
@@ -653,14 +654,19 @@ impl<'a> Parser<'a> {
             None
         };
 
-        self.expect_keyword_is(Keyword::END)?;
-        let has_end_case = self.parse_keyword(Keyword::CASE);
+        let end_token = Some(self.expect_keyword(Keyword::END)?.into());
+        let case_token = if self.parse_keyword(Keyword::CASE) {
+            Some(self.get_current_token().clone().into())
+        } else {
+            None
+        };
 
         Ok(Statement::Case(CaseStatement {
             match_expr,
             when_blocks,
             else_block,
-            has_end_case,
+            end_token,
+            case_token,
         }))
     }
 
@@ -672,6 +678,7 @@ impl<'a> Parser<'a> {
         let if_block = self.parse_conditional_statements(
             ConditionalStatementKind::If,
             &[Keyword::ELSE, Keyword::ELSEIF, Keyword::END],
+            true,
         )?;
 
         let elseif_blocks = if self.parse_keyword(Keyword::ELSEIF) {
@@ -679,6 +686,7 @@ impl<'a> Parser<'a> {
                 parser.parse_conditional_statements(
                     ConditionalStatementKind::ElseIf,
                     &[Keyword::ELSEIF, Keyword::ELSE, Keyword::END],
+                    true,
                 )
             })?
         } else {
@@ -691,12 +699,52 @@ impl<'a> Parser<'a> {
             None
         };
 
-        self.expect_keywords(&[Keyword::END, Keyword::IF])?;
+        let end_token = Some(self.expect_keyword(Keyword::END)?.into());
+        let if_token = Some(self.expect_keyword(Keyword::IF)?.into());
 
         Ok(Statement::If(IfStatement {
             if_block,
             elseif_blocks,
             else_block,
+            end_token,
+            if_token,
+        }))
+    }
+
+    /// Parse an MSSQL `IF` statement.
+    ///
+    /// See [Statement::If]
+    pub fn parse_mssql_if_stmt(&mut self) -> Result<Statement, ParserError> {
+        self.expect_keyword_is(Keyword::IF)?;
+        let condition = self.parse_expr()?;
+        let if_statement = self.parse_mssql_if_else_statement()?;
+
+        let semicolon_before_else = self.peek_token_ref().token == Token::SemiColon
+            && matches!(
+                &self.peek_nth_token_ref(1).token,
+                Token::Word(w) if w.keyword == Keyword::ELSE
+            );
+        if semicolon_before_else {
+            self.next_token();
+        }
+
+        let else_block = if self.parse_keyword(Keyword::ELSE) {
+            Some(vec![self.parse_mssql_if_else_statement()?])
+        } else {
+            None
+        };
+
+        Ok(Statement::If(IfStatement {
+            if_block: ConditionalStatements {
+                condition: Some(condition),
+                statements: vec![if_statement],
+                then_token: None,
+                kind: ConditionalStatementKind::If,
+            },
+            elseif_blocks: vec![],
+            else_block,
+            end_token: None,
+            if_token: None,
         }))
     }
 
@@ -711,18 +759,53 @@ impl<'a> Parser<'a> {
         &mut self,
         kind: ConditionalStatementKind,
         terminal_keywords: &[Keyword],
+        requires_then_token: bool,
     ) -> Result<ConditionalStatements, ParserError> {
         let condition = self.parse_expr()?;
-        self.expect_keyword_is(Keyword::THEN)?;
+        let then_token = if requires_then_token {
+            Some(self.expect_keyword(Keyword::THEN)?.into())
+        } else {
+            None
+        };
         let statements = self.parse_statement_list(terminal_keywords)?;
 
         Ok(ConditionalStatements {
-            condition,
+            condition: Some(condition),
             statements,
+            then_token,
             kind,
         })
     }
 
+    fn parse_mssql_if_else_statement(&mut self) -> Result<Statement, ParserError> {
+        if self.peek_keyword(Keyword::BEGIN) {
+            self.parse_mssql_begin_end_block()
+        } else {
+            self.parse_statement()
+        }
+    }
+
+    fn parse_mssql_begin_end_block(&mut self) -> Result<Statement, ParserError> {
+        self.expect_keyword_is(Keyword::BEGIN)?;
+
+        let mut statements = vec![];
+        while !self.peek_keyword(Keyword::END) {
+            statements.push(self.parse_statement()?);
+            let _ = self.consume_token(&Token::SemiColon);
+        }
+
+        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,
+        })
+    }
+
     /// Parse a `RAISE` statement.
     ///
     /// See [Statement::Raise]
diff --git a/tmp/agent-patch-flux-pr-1791.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 3653321..2b88447 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -14229,7 +14229,7 @@ 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_eq!(1, stmt.else_block.unwrap().len());
 
@@ -14280,8 +14280,8 @@ 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_eq!(1, stmt.else_block.unwrap().len());
 
     verified_stmt(concat!(
diff --git a/tmp/agent-patch-flux-pr-1791.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 5d76fd0..d181272 100644
--- a/tmp/agent-patch-flux-pr-1791.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -1915,6 +1915,34 @@ fn parse_mssql_table_identifier_with_default_schema() {
     ms().verified_stmt("SELECT * FROM mydatabase..MyTable");
 }
 
+#[test]
+fn parse_mssql_if_else_statement_single_statements() {
+    let sql = "IF @x = 1 SELECT 1 ELSE SELECT 2";
+    let Statement::If(stmt) = ms().verified_stmt(sql) else {
+        unreachable!();
+    };
+
+    assert_eq!(Some(Expr::BinaryOp {
+        left: Box::new(Expr::Identifier(Ident::new("@x"))),
+        op: BinaryOperator::Eq,
+        right: Box::new(Expr::value(number("1"))),
+    }), stmt.if_block.condition);
+    assert!(stmt.if_block.then_token.is_none());
+    assert!(stmt.end_token.is_none());
+    assert_eq!(1, stmt.if_block.statements.len());
+    assert_eq!(Some(1), stmt.else_block.map(|s| s.len()));
+}
+
+#[test]
+fn parse_mssql_if_else_statement_begin_end_blocks() {
+    ms().verified_stmt(
+        "IF @x = 1 BEGIN SELECT 1; SELECT 2; END ELSE BEGIN SELECT 3; SELECT 4; END",
+    );
+    ms().verified_stmt(
+        "IF @x = 1 BEGIN SELECT 1 SELECT 2 END ELSE BEGIN SELECT 3 SELECT 4 END",
+    );
+}
+
 fn ms() -> TestedDialects {
     TestedDialects::new(vec![Box::new(MsSqlDialect {})])
 }