agent_patch
runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-1500/flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/agent-logs/agent.patch
6085 bytes
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 505386f..d26cf04 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -561,6 +561,12 @@ pub enum Expr {
expr: Box<Expr>,
key: Ident,
},
+ /// Dialect-specific method invocation accessed through dot notation, e.g. `expr.value(...)`.
+ MethodCall {
+ expr: Box<Expr>,
+ method: Ident,
+ args: FunctionArgumentList,
+ },
/// `IS FALSE` operator
IsFalse(Box<Expr>),
/// `IS NOT FALSE` operator
@@ -1630,6 +1636,9 @@ impl fmt::Display for Expr {
Expr::CompositeAccess { expr, key } => {
write!(f, "{expr}.{key}")
}
+ Expr::MethodCall { expr, method, args } => {
+ write!(f, "{expr}.{method}({args})")
+ }
Expr::AtTimeZone {
timestamp,
time_zone,
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index a732aa5..ddacb66 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -279,6 +279,12 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect supports calling methods on expressions via dot notation,
+ /// for example `expr.value('(/foo)[1]', 'varchar(100)')`.
+ fn supports_expression_method_call(&self) -> bool {
+ false
+ }
+
/// Returns true if the dialect supports multiple variable assignment
/// using parentheses in a `SET` variable declaration.
///
@@ -451,6 +457,7 @@ pub trait Dialect: Debug + Any {
| Token::LBracket
| Token::Overlap
| Token::CaretAt => Ok(p!(DoubleColon)),
+ Token::Period if self.supports_expression_method_call() => Ok(p!(DoubleColon)),
Token::Arrow
| Token::LongArrow
| Token::HashArrow
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mssql.rs b/app/src/dialect/mssql.rs
index 8aab0bc..7876893 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mssql.rs
+++ b/app/src/dialect/mssql.rs
@@ -58,6 +58,10 @@ impl Dialect for MsSqlDialect {
true
}
+ fn supports_expression_method_call(&self) -> bool {
+ true
+ }
+
/// In MSSQL, there is no boolean type, and `true` and `false` are valid column names
fn supports_boolean_literals(&self) -> bool {
false
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 4115bbc..06617a2 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -1319,6 +1319,8 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?;
if !self.consume_token(&Token::Period) {
Ok(expr)
+ } else if self.dialect.supports_expression_method_call() && self.peek_method_call() {
+ self.parse_expression_method_call(expr)
} else {
let tok = self.next_token();
let key = match tok.token {
@@ -2881,6 +2883,11 @@ impl<'a> Parser<'a> {
op: UnaryOperator::PGPostfixFactorial,
expr: Box::new(expr),
})
+ } else if Token::Period == tok
+ && self.dialect.supports_expression_method_call()
+ && self.peek_method_call()
+ {
+ self.parse_expression_method_call(expr)
} else if Token::LBracket == tok {
if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect) {
self.parse_subscript(expr)
@@ -2902,6 +2909,24 @@ impl<'a> Parser<'a> {
}
}
+ fn peek_method_call(&self) -> bool {
+ matches!(
+ self.peek_token().token,
+ Token::Word(_) | Token::SingleQuotedString(_) | Token::DoubleQuotedString(_)
+ ) && self.peek_nth_token(1).token == Token::LParen
+ }
+
+ fn parse_expression_method_call(&mut self, expr: Expr) -> Result<Expr, ParserError> {
+ let method = self.parse_identifier(false)?;
+ self.expect_token(&Token::LParen)?;
+ let args = self.parse_function_argument_list()?;
+ Ok(Expr::MethodCall {
+ expr: Box::new(expr),
+ method,
+ args,
+ })
+ }
+
/// Parse the `ESCAPE CHAR` portion of `LIKE`, `ILIKE`, and `SIMILAR TO`
pub fn parse_escape_char(&mut self) -> Result<Option<String>, ParserError> {
if self.parse_keyword(Keyword::ESCAPE) {
diff --git a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index c28f89e..8ee0f98 100644
--- a/tmp/agent-patch-flux-pr-1500.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -1445,6 +1445,24 @@ fn parse_true_false_as_identifiers() {
);
}
+#[test]
+fn parse_mssql_dot_method_call() {
+ let sql = "CONVERT(XML, '<root/>').value('(/root)[1]', 'varchar(10)')";
+ let expr = ms().verified_expr(sql);
+ let Expr::MethodCall {
+ expr: target,
+ method,
+ args,
+ } = expr else {
+ panic!("expected method call");
+ };
+ assert_eq!(Ident::new("value"), method);
+ assert_eq!(2, args.args.len());
+ assert_eq!("'(/root)[1]'", args.args[0].to_string());
+ assert_eq!("'varchar(10)'", args.args[1].to_string());
+ assert!(matches!(*target, Expr::Convert { .. }));
+}
+
fn ms() -> TestedDialects {
TestedDialects::new(vec![Box::new(MsSqlDialect {})])
}