STET

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

Back to adjudication
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");