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
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";