agent_patch
runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1900/flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch
19772 bytes
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/ddl.rs b/app/src/ast/ddl.rs
index 059c619..fd0ca21 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/ddl.rs
+++ b/app/src/ast/ddl.rs
@@ -31,7 +31,7 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::ast::value::escape_single_quote_string;
use crate::ast::{
display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
- CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
+ CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull, IndexColumn,
FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
ValueWithSpan,
@@ -978,8 +978,8 @@ pub enum TableConstraint {
///
/// [1]: IndexType
index_type: Option<IndexType>,
- /// Identifiers of the columns that are unique.
- columns: Vec<Ident>,
+ /// Index columns that are unique.
+ columns: Vec<IndexColumn>,
index_options: Vec<IndexOption>,
characteristics: Option<ConstraintCharacteristics>,
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
@@ -1014,8 +1014,8 @@ pub enum TableConstraint {
///
/// [1]: IndexType
index_type: Option<IndexType>,
- /// Identifiers of the columns that form the primary key.
- columns: Vec<Ident>,
+ /// Index columns that form the primary key.
+ columns: Vec<IndexColumn>,
index_options: Vec<IndexOption>,
characteristics: Option<ConstraintCharacteristics>,
},
@@ -1059,8 +1059,8 @@ pub enum TableConstraint {
///
/// [1]: IndexType
index_type: Option<IndexType>,
- /// Referred column identifier list.
- columns: Vec<Ident>,
+ /// Indexed column list.
+ columns: Vec<IndexColumn>,
},
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
/// and MySQL displays both the same way, it is part of this definition as well.
@@ -1069,8 +1069,6 @@ pub enum TableConstraint {
///
/// ```markdown
/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
- ///
- /// key_part: col_name
/// ```
///
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
@@ -1082,8 +1080,8 @@ pub enum TableConstraint {
index_type_display: KeyOrIndexDisplay,
/// Optional index name.
opt_index_name: Option<Ident>,
- /// Referred column identifier list.
- columns: Vec<Ident>,
+ /// Indexed column list.
+ columns: Vec<IndexColumn>,
},
}
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 14664b4..63724a5 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -650,7 +650,7 @@ impl Spanned for TableConstraint {
name.iter()
.map(|i| i.span)
.chain(index_name.iter().map(|i| i.span))
- .chain(columns.iter().map(|i| i.span))
+ .chain(columns.iter().map(|i| i.column.span()))
.chain(characteristics.iter().map(|i| i.span())),
),
TableConstraint::PrimaryKey {
@@ -664,7 +664,7 @@ impl Spanned for TableConstraint {
name.iter()
.map(|i| i.span)
.chain(index_name.iter().map(|i| i.span))
- .chain(columns.iter().map(|i| i.span))
+ .chain(columns.iter().map(|i| i.column.span()))
.chain(characteristics.iter().map(|i| i.span())),
),
TableConstraint::ForeignKey {
@@ -700,7 +700,7 @@ impl Spanned for TableConstraint {
} => union_spans(
name.iter()
.map(|i| i.span)
- .chain(columns.iter().map(|i| i.span)),
+ .chain(columns.iter().map(|i| i.column.span())),
),
TableConstraint::FulltextOrSpatial {
fulltext: _,
@@ -711,7 +711,7 @@ impl Spanned for TableConstraint {
opt_index_name
.iter()
.map(|i| i.span)
- .chain(columns.iter().map(|i| i.span)),
+ .chain(columns.iter().map(|i| i.column.span())),
),
}
}
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 44bf58d..6ea4004 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -8070,7 +8070,7 @@ impl<'a> Parser<'a> {
let index_name = self.parse_optional_ident()?;
let index_type = self.parse_optional_using_then_index_type()?;
- let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
+ let columns = self.parse_parenthesized_index_column_list()?;
let index_options = self.parse_index_options()?;
let characteristics = self.parse_constraint_characteristics()?;
Ok(Some(TableConstraint::Unique {
@@ -8092,7 +8092,7 @@ impl<'a> Parser<'a> {
let index_name = self.parse_optional_ident()?;
let index_type = self.parse_optional_using_then_index_type()?;
- let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
+ let columns = self.parse_parenthesized_index_column_list()?;
let index_options = self.parse_index_options()?;
let characteristics = self.parse_constraint_characteristics()?;
Ok(Some(TableConstraint::PrimaryKey {
@@ -8170,7 +8170,7 @@ impl<'a> Parser<'a> {
};
let index_type = self.parse_optional_using_then_index_type()?;
- let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
+ let columns = self.parse_parenthesized_index_column_list()?;
Ok(Some(TableConstraint::Index {
display_as_key,
@@ -8199,7 +8199,7 @@ impl<'a> Parser<'a> {
let opt_index_name = self.parse_optional_ident()?;
- let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
+ let columns = self.parse_parenthesized_index_column_list()?;
Ok(Some(TableConstraint::FulltextOrSpatial {
fulltext,
@@ -14855,6 +14855,10 @@ impl<'a> Parser<'a> {
})
}
+ fn parse_parenthesized_index_column_list(&mut self) -> Result<Vec<IndexColumn>, ParserError> {
+ self.parse_parenthesized(|parser| parser.parse_comma_separated(Parser::parse_create_index_expr))
+ }
+
fn parse_order_by_expr_inner(
&mut self,
with_operator_class: bool,
@@ -16530,6 +16534,18 @@ mod tests {
let dialect =
TestedDialects::new(vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})]);
+ let index_column = |name: &str| IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Identifier(Ident::new(name)),
+ options: OrderByOptions {
+ asc: None,
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: None,
+ };
+
test_parse_table_constraint!(
dialect,
"INDEX (c1)",
@@ -16537,7 +16553,7 @@ mod tests {
display_as_key: false,
name: None,
index_type: None,
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
@@ -16548,7 +16564,7 @@ mod tests {
display_as_key: true,
name: None,
index_type: None,
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
@@ -16559,7 +16575,7 @@ mod tests {
display_as_key: false,
name: Some(Ident::with_quote('\'', "index")),
index_type: None,
- columns: vec![Ident::new("c1"), Ident::new("c2")],
+ columns: vec![index_column("c1"), index_column("c2")],
}
);
@@ -16570,7 +16586,7 @@ mod tests {
display_as_key: false,
name: None,
index_type: Some(IndexType::BTree),
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
@@ -16581,7 +16597,7 @@ mod tests {
display_as_key: false,
name: None,
index_type: Some(IndexType::Hash),
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
@@ -16592,7 +16608,7 @@ mod tests {
display_as_key: false,
name: Some(Ident::new("idx_name")),
index_type: Some(IndexType::BTree),
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
@@ -16603,7 +16619,7 @@ mod tests {
display_as_key: false,
name: Some(Ident::new("idx_name")),
index_type: Some(IndexType::Hash),
- columns: vec![Ident::new("c1")],
+ columns: vec![index_column("c1")],
}
);
}
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 540348f..eb38419 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -41,6 +41,20 @@ fn mysql_and_generic() -> TestedDialects {
TestedDialects::new(vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})])
}
+fn index_column(name: &str) -> IndexColumn {
+ IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Identifier(Ident::new(name)),
+ options: OrderByOptions {
+ asc: None,
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: None,
+ }
+}
+
#[test]
fn parse_identifiers() {
mysql().verified_stmt("SELECT $a$, àà");
@@ -665,7 +679,7 @@ fn table_constraint_unique_primary_ctor(
name: Option<Ident>,
index_name: Option<Ident>,
index_type: Option<IndexType>,
- columns: Vec<Ident>,
+ columns: Vec<IndexColumn>,
index_options: Vec<IndexOption>,
characteristics: Option<ConstraintCharacteristics>,
unique_index_type_display: Option<KeyOrIndexDisplay>,
@@ -713,7 +727,7 @@ fn parse_create_table_primary_and_unique_key() {
Some(Ident::new("bar_key")),
None,
None,
- vec![Ident::new("bar")],
+ vec![index_column("bar")],
vec![],
None,
index_type_display,
@@ -776,7 +790,7 @@ fn parse_create_table_primary_and_unique_key_with_index_options() {
Some(Ident::new("constr")),
Some(Ident::new("index_name")),
None,
- vec![Ident::new("bar"), Ident::new("var")],
+ vec![index_column("bar"), index_column("var")],
vec![
IndexOption::Using(IndexType::Hash),
IndexOption::Comment("yes, ".into()),
@@ -814,7 +828,7 @@ fn parse_create_table_primary_and_unique_key_with_index_type() {
None,
Some(Ident::new("index_name")),
Some(IndexType::BTree),
- vec![Ident::new("bar")],
+ vec![index_column("bar")],
vec![IndexOption::Using(IndexType::Hash)],
None,
index_type_display,
@@ -841,6 +855,135 @@ fn parse_create_table_primary_and_unique_key_characteristic_test() {
}
}
+#[test]
+fn parse_create_table_unique_and_primary_key_with_index_columns() {
+ let sql = "CREATE TABLE foo (bar INT, baz INT, UNIQUE (bar DESC NULLS LAST, (baz + 1) opclass))";
+ match mysql_and_generic().verified_stmt(sql) {
+ Statement::CreateTable(CreateTable { constraints, .. }) => {
+ assert_eq!(
+ constraints,
+ vec![TableConstraint::Unique {
+ name: None,
+ index_name: None,
+ index_type_display: KeyOrIndexDisplay::None,
+ index_type: None,
+ columns: vec![
+ IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Identifier(Ident::new("bar")),
+ options: OrderByOptions {
+ asc: Some(false),
+ nulls_first: Some(false),
+ },
+ with_fill: None,
+ },
+ operator_class: None,
+ },
+ IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Nested(Box::new(Expr::BinaryOp {
+ left: Box::new(Expr::Identifier(Ident::new("baz"))),
+ op: BinaryOperator::Plus,
+ right: Box::new(Expr::Value(number("1").with_empty_span())),
+ })),
+ options: OrderByOptions {
+ asc: None,
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: Some(Ident::new("opclass")),
+ },
+ ],
+ index_options: vec![],
+ characteristics: None,
+ nulls_distinct: NullsDistinctOption::None,
+ }]
+ );
+ }
+ _ => unreachable!(),
+ }
+
+ let sql = "CREATE TABLE foo (bar INT, PRIMARY KEY (bar DESC))";
+ match mysql_and_generic().verified_stmt(sql) {
+ Statement::CreateTable(CreateTable { constraints, .. }) => {
+ assert_eq!(
+ constraints,
+ vec![TableConstraint::PrimaryKey {
+ name: None,
+ index_name: None,
+ index_type: None,
+ columns: vec![IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Identifier(Ident::new("bar")),
+ options: OrderByOptions {
+ asc: Some(false),
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: None,
+ }],
+ index_options: vec![],
+ characteristics: None,
+ }]
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn parse_create_table_index_and_fulltext_with_index_columns() {
+ let sql = "CREATE TABLE tb (id INT, INDEX idx ((id + 1) opclass DESC), FULLTEXT KEY ft (id DESC))";
+ match mysql_and_generic().verified_stmt(sql) {
+ Statement::CreateTable(CreateTable { constraints, .. }) => {
+ assert_eq!(
+ constraints,
+ vec![
+ TableConstraint::Index {
+ display_as_key: false,
+ name: Some(Ident::new("idx")),
+ index_type: None,
+ columns: vec![IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Nested(Box::new(Expr::BinaryOp {
+ left: Box::new(Expr::Identifier(Ident::new("id"))),
+ op: BinaryOperator::Plus,
+ right: Box::new(Expr::Value(number("1").with_empty_span())),
+ })),
+ options: OrderByOptions {
+ asc: Some(false),
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: Some(Ident::new("opclass")),
+ }],
+ },
+ TableConstraint::FulltextOrSpatial {
+ fulltext: true,
+ index_type_display: KeyOrIndexDisplay::Key,
+ opt_index_name: Some(Ident::new("ft")),
+ columns: vec![IndexColumn {
+ column: OrderByExpr {
+ expr: Expr::Identifier(Ident::new("id")),
+ options: OrderByOptions {
+ asc: Some(false),
+ nulls_first: None,
+ },
+ with_fill: None,
+ },
+ operator_class: None,
+ }],
+ },
+ ]
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
#[test]
fn parse_create_table_comment() {
let without_equal = "CREATE TABLE foo (bar INT) COMMENT 'baz'";
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index b6605cf..3552a11 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -619,6 +619,25 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE (c)");
}
+#[test]
+fn parse_alter_table_constraint_with_index_columns() {
+ match pg_and_generic().verified_stmt(
+ "ALTER TABLE t ADD CONSTRAINT b UNIQUE ((lower(c)) text_pattern_ops DESC NULLS FIRST)",
+ ) {
+ Statement::AlterTable { operations, .. } => match &operations[0] {
+ AlterTableOperation::AddConstraint(TableConstraint::Unique { columns, .. }) => {
+ assert_eq!(columns.len(), 1);
+ assert_eq!(
+ columns[0].to_string(),
+ "(lower(c)) text_pattern_ops DESC NULLS FIRST"
+ );
+ }
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ }
+}
+
#[test]
fn parse_alter_table_disable() {
pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");