STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1500/flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch

8843 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 505386f..9b454f2 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -808,6 +808,8 @@ pub enum Expr {
     },
     /// Scalar function call e.g. `LEFT(foo, 5)`
     Function(Function),
+    /// Dialect-specific method call on an expression, e.g. `expr.value('x', 'int')`
+    Method(Method),
     /// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
     ///
     /// Note we only recognize a complete single expression as `<condition>`,
@@ -1464,6 +1466,7 @@ impl fmt::Display for Expr {
                 write!(f, " '{}'", &value::escape_single_quote_string(value))
             }
             Expr::Function(fun) => write!(f, "{fun}"),
+            Expr::Method(method) => write!(f, "{method}"),
             Expr::Case {
                 operand,
                 conditions,
@@ -5502,6 +5505,21 @@ impl fmt::Display for Function {
     }
 }
 
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct Method {
+    pub expr: Box<Expr>,
+    pub name: Ident,
+    pub args: FunctionArgumentList,
+}
+
+impl fmt::Display for Method {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}.{}({})", self.expr, self.name, self.args)
+    }
+}
+
 /// The arguments passed to a function call.
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index a732aa5..ecdf3d4 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -333,6 +333,12 @@ pub trait Dialect: Debug + Any {
         self.supports_trailing_commas()
     }
 
+    /// Returns true if the dialect supports calling methods on arbitrary
+    /// expressions using dot notation, e.g. `expr.value(...)`.
+    fn supports_methods_on_expr(&self) -> bool {
+        false
+    }
+
     /// Dialect-specific infix parser override
     ///
     /// This method is called to parse the next infix expression.
@@ -380,6 +386,13 @@ pub trait Dialect: Debug + Any {
         let token = parser.peek_token();
         debug!("get_next_precedence_full() {:?}", token);
         match token.token {
+            Token::Period
+                if self.supports_methods_on_expr()
+                    && matches!(parser.peek_nth_token(1).token, Token::Word(_))
+                    && parser.peek_nth_token(2).token == Token::LParen =>
+            {
+                Ok(p!(DoubleColon))
+            }
             Token::Word(w) if w.keyword == Keyword::OR => Ok(p!(Or)),
             Token::Word(w) if w.keyword == Keyword::AND => Ok(p!(And)),
             Token::Word(w) if w.keyword == Keyword::XOR => Ok(p!(Xor)),
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mssql.rs b/app/src/dialect/mssql.rs
index 8aab0bc..6aca2dd 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/dialect/mssql.rs
+++ b/app/src/dialect/mssql.rs
@@ -62,4 +62,8 @@ impl Dialect for MsSqlDialect {
     fn supports_boolean_literals(&self) -> bool {
         false
     }
+
+    fn supports_methods_on_expr(&self) -> bool {
+        true
+    }
 }
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 4115bbc..0114738 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -2881,6 +2881,8 @@ impl<'a> Parser<'a> {
                 op: UnaryOperator::PGPostfixFactorial,
                 expr: Box::new(expr),
             })
+        } else if Token::Period == tok && self.dialect.supports_methods_on_expr() {
+            self.parse_method(expr)
         } else if Token::LBracket == tok {
             if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect) {
                 self.parse_subscript(expr)
@@ -2911,6 +2913,16 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_method(&mut self, expr: Expr) -> Result<Expr, ParserError> {
+        let name = self.parse_identifier(false)?;
+        self.expect_token(&Token::LParen)?;
+        Ok(Expr::Method(Method {
+            expr: Box::new(expr),
+            name,
+            args: self.parse_function_argument_list()?,
+        }))
+    }
+
     /// Parses an array subscript like
     /// * `[:]`
     /// * `[l]`
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index c28f89e..4dc8570 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -29,7 +29,7 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment;
 use sqlparser::ast::Value::SingleQuotedString;
 use sqlparser::ast::*;
 use sqlparser::dialect::{GenericDialect, MsSqlDialect};
-use sqlparser::parser::ParserError;
+use sqlparser::parser::{Parser, ParserError};
 
 #[test]
 fn parse_mssql_identifiers() {
@@ -528,6 +528,98 @@ fn parse_mssql_openjson() {
     );
 }
 
+#[test]
+fn parse_mssql_expression_method_call() {
+    let select = ms().verified_only_select(
+        "SELECT CONVERT(XML, payload).value('(/root/@id)[1]', 'int') FROM events",
+    );
+
+    assert_eq!(
+        expr_from_projection(&select.projection[0]),
+        &Expr::Method(Method {
+            expr: Box::new(Expr::Convert {
+                is_try: false,
+                expr: Box::new(Expr::Identifier(Ident::new("payload"))),
+                data_type: Some(DataType::Custom(
+                    ObjectName(vec![Ident::new("XML")]),
+                    vec![],
+                )),
+                charset: None,
+                target_before_value: true,
+                styles: vec![],
+            }),
+            name: Ident::new("value"),
+            args: FunctionArgumentList {
+                duplicate_treatment: None,
+                args: vec![
+                    FunctionArg::Unnamed(Expr::Value(SingleQuotedString("(/root/@id)[1]".into())).into()),
+                    FunctionArg::Unnamed(Expr::Value(SingleQuotedString("int".into())).into()),
+                ],
+                clauses: vec![],
+            },
+        })
+    );
+}
+
+#[test]
+fn parse_mssql_chained_expression_methods() {
+    let select = ms().verified_only_select(
+        "SELECT CONVERT(XML, payload).query('/root').value('.', 'nvarchar(max)') FROM events",
+    );
+
+    assert_eq!(
+        expr_from_projection(&select.projection[0]),
+        &Expr::Method(Method {
+            expr: Box::new(Expr::Method(Method {
+                expr: Box::new(Expr::Convert {
+                    is_try: false,
+                    expr: Box::new(Expr::Identifier(Ident::new("payload"))),
+                    data_type: Some(DataType::Custom(
+                        ObjectName(vec![Ident::new("XML")]),
+                        vec![],
+                    )),
+                    charset: None,
+                    target_before_value: true,
+                    styles: vec![],
+                }),
+                name: Ident::new("query"),
+                args: FunctionArgumentList {
+                    duplicate_treatment: None,
+                    args: vec![FunctionArg::Unnamed(
+                        Expr::Value(SingleQuotedString("/root".into())).into(),
+                    )],
+                    clauses: vec![],
+                },
+            })),
+            name: Ident::new("value"),
+            args: FunctionArgumentList {
+                duplicate_treatment: None,
+                args: vec![
+                    FunctionArg::Unnamed(Expr::Value(SingleQuotedString(".".into())).into()),
+                    FunctionArg::Unnamed(
+                        Expr::Value(SingleQuotedString("nvarchar(max)".into())).into(),
+                    ),
+                ],
+                clauses: vec![],
+            },
+        })
+    );
+}
+
+#[test]
+fn mssql_expression_methods_are_dialect_gated() {
+    let err = Parser::parse_sql(
+        &GenericDialect {},
+        "SELECT CONVERT(XML, payload).value('.', 'int') FROM events",
+    )
+    .unwrap_err();
+
+    assert!(
+        err.to_string().contains("Expected end of statement"),
+        "{err}"
+    );
+}
+
 #[test]
 fn parse_mssql_top_paren() {
     let sql = "SELECT TOP (5) * FROM foo";