STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-3-codex/flux-pr-1900/flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/agent-logs/agent.patch

13766 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/ddl.rs b/app/src/ast/ddl.rs
index 059c619..c7ef9f7 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/ast/ddl.rs
+++ b/app/src/ast/ddl.rs
@@ -32,9 +32,9 @@ use crate::ast::value::escape_single_quote_string;
 use crate::ast::{
     display_comma_separated, display_separated, CommentDef, CreateFunctionBody,
     CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
-    FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName,
-    OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
-    ValueWithSpan,
+    FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition,
+    ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption,
+    Tag, Value, ValueWithSpan,
 };
 use crate::keywords::Keyword;
 use crate::tokenizer::Token;
@@ -978,8 +978,8 @@ pub enum TableConstraint {
         ///
         /// [1]: IndexType
         index_type: Option<IndexType>,
-        /// Identifiers of the columns that are unique.
-        columns: Vec<Ident>,
+        /// Column expressions 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>,
+        /// Column expressions 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>,
+        /// Referred index 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.
@@ -1082,8 +1082,8 @@ pub enum TableConstraint {
         index_type_display: KeyOrIndexDisplay,
         /// Optional index name.
         opt_index_name: Option<Ident>,
-        /// Referred column identifier list.
-        columns: Vec<Ident>,
+        /// Referred index 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-3-codex/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-3-codex/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-3-codex/app/src/parser/mod.rs b/app/src/parser/mod.rs
index 44bf58d..b31734a 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -8070,7 +8070,11 @@ 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_column_list_inner(
+                    Mandatory,
+                    false,
+                    Parser::parse_create_index_expr,
+                )?;
                 let index_options = self.parse_index_options()?;
                 let characteristics = self.parse_constraint_characteristics()?;
                 Ok(Some(TableConstraint::Unique {
@@ -8092,7 +8096,11 @@ 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_column_list_inner(
+                    Mandatory,
+                    false,
+                    Parser::parse_create_index_expr,
+                )?;
                 let index_options = self.parse_index_options()?;
                 let characteristics = self.parse_constraint_characteristics()?;
                 Ok(Some(TableConstraint::PrimaryKey {
@@ -8170,7 +8178,11 @@ 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_column_list_inner(
+                    Mandatory,
+                    false,
+                    Parser::parse_create_index_expr,
+                )?;
 
                 Ok(Some(TableConstraint::Index {
                     display_as_key,
@@ -8199,7 +8211,11 @@ 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_column_list_inner(
+                    Mandatory,
+                    false,
+                    Parser::parse_create_index_expr,
+                )?;
 
                 Ok(Some(TableConstraint::FulltextOrSpatial {
                     fulltext,
@@ -16515,6 +16531,20 @@ mod tests {
 
     #[test]
     fn mysql_parse_index_table_constraint() {
+        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,
+            }
+        }
+
         macro_rules! test_parse_table_constraint {
             ($dialect:expr, $input:expr, $expected:expr $(,)?) => {{
                 $dialect.run_parser_method(&*$input, |parser| {
@@ -16537,7 +16567,7 @@ mod tests {
                 display_as_key: false,
                 name: None,
                 index_type: None,
-                columns: vec![Ident::new("c1")],
+                columns: vec![index_column("c1")],
             }
         );
 
@@ -16548,7 +16578,7 @@ mod tests {
                 display_as_key: true,
                 name: None,
                 index_type: None,
-                columns: vec![Ident::new("c1")],
+                columns: vec![index_column("c1")],
             }
         );
 
@@ -16559,7 +16589,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 +16600,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 +16611,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 +16622,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 +16633,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-3-codex/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 5205460..ce05f13 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -4947,7 +4947,9 @@ fn parse_alter_table_constraints() {
     check_one("CONSTRAINT ck CHECK (rtrim(ltrim(REF_CODE)) <> '')");
 
     check_one("PRIMARY KEY (foo, bar)");
+    check_one("PRIMARY KEY (foo DESC, bar ASC NULLS LAST)");
     check_one("UNIQUE (id)");
+    check_one("UNIQUE (name DESC NULLS FIRST)");
     check_one("FOREIGN KEY (foo, bar) REFERENCES AnotherTable(foo, bar)");
     check_one("CHECK (end_date > start_date OR end_date IS NULL)");
     check_one("CONSTRAINT fk FOREIGN KEY (lng) REFERENCES othertable4");
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 540348f..114ac9a 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -670,6 +670,21 @@ fn table_constraint_unique_primary_ctor(
     characteristics: Option<ConstraintCharacteristics>,
     unique_index_type_display: Option<KeyOrIndexDisplay>,
 ) -> TableConstraint {
+    let columns = columns
+        .into_iter()
+        .map(|column| IndexColumn {
+            column: OrderByExpr {
+                expr: Expr::Identifier(column),
+                options: OrderByOptions {
+                    asc: None,
+                    nulls_first: None,
+                },
+                with_fill: None,
+            },
+            operator_class: None,
+        })
+        .collect();
+
     match unique_index_type_display {
         Some(index_type_display) => TableConstraint::Unique {
             name,
diff --git a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index b6605cf..acfa6ca 100644
--- a/tmp/agent-patch-flux-pr-1900.1-of-1.2026-02-28__18-27-22__gpt-5-3-codex/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -619,6 +619,33 @@ 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_column_options() {
+    match pg().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE (c text_ops DESC NULLS LAST)")
+    {
+        Statement::AlterTable { operations, .. } => match &operations[0] {
+            AlterTableOperation::AddConstraint(TableConstraint::Unique { columns, .. }) => {
+                assert_eq!(
+                    columns,
+                    &vec![IndexColumn {
+                        column: OrderByExpr {
+                            expr: Expr::Identifier(Ident::new("c")),
+                            options: OrderByOptions {
+                                asc: Some(false),
+                                nulls_first: Some(false),
+                            },
+                            with_fill: None,
+                        },
+                        operator_class: Some(Ident::new("text_ops")),
+                    }]
+                );
+            }
+            _ => unreachable!(),
+        },
+        _ => unreachable!(),
+    }
+}
+
 #[test]
 fn parse_alter_table_disable() {
     pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");