STET

agent_patch

runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1414/flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch

18537 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 480442b..d56498d 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -5728,14 +5728,56 @@ pub struct HiveFormat {
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
-pub struct SqlOption {
-    pub name: Ident,
-    pub value: Expr,
+pub enum SqlOption {
+    KeyValue { name: Ident, value: Expr },
+    ClusteredIndex(Vec<OrderByExpr>),
+    ClusteredColumnstoreIndex,
+    Heap,
+    Partition {
+        column: Ident,
+        range_direction: PartitionRangeDirection,
+        for_values: Vec<Expr>,
+    },
 }
 
 impl fmt::Display for SqlOption {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{} = {}", self.name, self.value)
+        match self {
+            SqlOption::KeyValue { name, value } => write!(f, "{name} = {value}"),
+            SqlOption::ClusteredIndex(columns) => {
+                write!(f, "CLUSTERED INDEX ({})", display_comma_separated(columns))
+            }
+            SqlOption::ClusteredColumnstoreIndex => f.write_str("CLUSTERED COLUMNSTORE INDEX"),
+            SqlOption::Heap => f.write_str("HEAP"),
+            SqlOption::Partition {
+                column,
+                range_direction,
+                for_values,
+            } => {
+                write!(
+                    f,
+                    "PARTITION ({column} RANGE {range_direction} FOR VALUES ({}))",
+                    display_comma_separated(for_values)
+                )
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum PartitionRangeDirection {
+    Left,
+    Right,
+}
+
+impl fmt::Display for PartitionRangeDirection {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            PartitionRangeDirection::Left => f.write_str("LEFT"),
+            PartitionRangeDirection::Right => f.write_str("RIGHT"),
+        }
     }
 }
 
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index b39685c..45c6a2b 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -6480,10 +6480,75 @@ impl<'a> Parser<'a> {
     }
 
     pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
+        if self.parse_keyword(Keyword::CLUSTERED) {
+            if self.parse_keyword(Keyword::INDEX) {
+                self.expect_token(&Token::LParen)?;
+                let columns = self.parse_comma_separated(Parser::parse_order_by_expr)?;
+                self.expect_token(&Token::RParen)?;
+                return Ok(SqlOption::ClusteredIndex(columns));
+            }
+
+            let columnstore = self.parse_unquoted_word("COLUMNSTORE")?;
+            if !columnstore.eq_ignore_ascii_case("COLUMNSTORE") {
+                return self.expected("INDEX or COLUMNSTORE after CLUSTERED", self.peek_token());
+            }
+            self.expect_keyword(Keyword::INDEX)?;
+            return Ok(SqlOption::ClusteredColumnstoreIndex);
+        }
+
+        if self.parse_unquoted_word_if_eq("HEAP") {
+            return Ok(SqlOption::Heap);
+        }
+
+        if self.parse_keyword(Keyword::PARTITION) {
+            self.expect_token(&Token::LParen)?;
+            let column = self.parse_identifier(false)?;
+            self.expect_keyword(Keyword::RANGE)?;
+            let range_direction = match self.parse_one_of_keywords(&[Keyword::LEFT, Keyword::RIGHT])
+            {
+                Some(Keyword::LEFT) => PartitionRangeDirection::Left,
+                Some(Keyword::RIGHT) => PartitionRangeDirection::Right,
+                _ => return self.expected("LEFT or RIGHT after RANGE", self.peek_token()),
+            };
+            self.expect_keyword(Keyword::FOR)?;
+            self.expect_keyword(Keyword::VALUES)?;
+            self.expect_token(&Token::LParen)?;
+            let for_values = self.parse_comma_separated(Parser::parse_expr)?;
+            self.expect_token(&Token::RParen)?;
+            self.expect_token(&Token::RParen)?;
+            return Ok(SqlOption::Partition {
+                column,
+                range_direction,
+                for_values,
+            });
+        }
+
         let name = self.parse_identifier(false)?;
         self.expect_token(&Token::Eq)?;
         let value = self.parse_expr()?;
-        Ok(SqlOption { name, value })
+        Ok(SqlOption::KeyValue { name, value })
+    }
+
+    fn parse_unquoted_word(&mut self, expected: &str) -> Result<String, ParserError> {
+        match self.next_token().token {
+            Token::Word(word) if word.quote_style.is_none() => Ok(word.value),
+            _ => self.expected(expected, self.peek_token()),
+        }
+    }
+
+    fn parse_unquoted_word_if_eq(&mut self, expected: &str) -> bool {
+        let [first, second] = self.peek_tokens::<2>();
+        match first {
+            Token::Word(ref word)
+                if word.quote_style.is_none()
+                    && word.value.eq_ignore_ascii_case(expected)
+                    && second != Token::Eq =>
+            {
+                self.next_token();
+                true
+            }
+            _ => false,
+        }
     }
 
     pub fn parse_partition(&mut self) -> Result<Partition, ParserError> {
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_bigquery.rs b/app/tests/sqlparser_bigquery.rs
index 4f84b37..56ad430 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_bigquery.rs
+++ b/app/tests/sqlparser_bigquery.rs
@@ -267,7 +267,7 @@ fn parse_create_view_with_options() {
                     ViewColumnDef {
                         name: Ident::new("age"),
                         data_type: None,
-                        options: Some(vec![SqlOption {
+                        options: Some(vec![SqlOption::KeyValue {
                             name: Ident::new("description"),
                             value: Expr::Value(Value::DoubleQuotedString("field age".to_string())),
                         }])
@@ -287,7 +287,7 @@ fn parse_create_view_with_options() {
                 unreachable!()
             };
             assert_eq!(
-                &SqlOption {
+                &SqlOption::KeyValue {
                     name: Ident::new("description"),
                     value: Expr::Value(Value::DoubleQuotedString(
                         "a view that expires in 2 days".to_string()
@@ -414,7 +414,7 @@ fn parse_create_table_with_options() {
                             },
                             ColumnOptionDef {
                                 name: None,
-                                option: ColumnOption::Options(vec![SqlOption {
+                                option: ColumnOption::Options(vec![SqlOption::KeyValue {
                                     name: Ident::new("description"),
                                     value: Expr::Value(Value::DoubleQuotedString(
                                         "field x".to_string()
@@ -429,7 +429,7 @@ fn parse_create_table_with_options() {
                         collation: None,
                         options: vec![ColumnOptionDef {
                             name: None,
-                            option: ColumnOption::Options(vec![SqlOption {
+                            option: ColumnOption::Options(vec![SqlOption::KeyValue {
                                 name: Ident::new("description"),
                                 value: Expr::Value(Value::DoubleQuotedString(
                                     "field y".to_string()
@@ -448,11 +448,11 @@ fn parse_create_table_with_options() {
                         Ident::new("age"),
                     ])),
                     Some(vec![
-                        SqlOption {
+                        SqlOption::KeyValue {
                             name: Ident::new("partition_expiration_days"),
                             value: Expr::Value(number("1")),
                         },
-                        SqlOption {
+                        SqlOption::KeyValue {
                             name: Ident::new("description"),
                             value: Expr::Value(Value::DoubleQuotedString(
                                 "table option description".to_string()
@@ -2005,7 +2005,7 @@ fn test_bigquery_create_function() {
             function_body: Some(CreateFunctionBody::AsAfterOptions(Expr::Value(number(
                 "42"
             )))),
-            options: Some(vec![SqlOption {
+            options: Some(vec![SqlOption::KeyValue {
                 name: Ident::new("x"),
                 value: Expr::Value(Value::SingleQuotedString("y".into())),
             }]),
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index bebd33d..ea85d9c 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -3637,11 +3637,11 @@ fn parse_create_table_with_options() {
         Statement::CreateTable(CreateTable { with_options, .. }) => {
             assert_eq!(
                 vec![
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "foo".into(),
                         value: Expr::Value(Value::SingleQuotedString("bar".into())),
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "a".into(),
                         value: Expr::Value(number("123")),
                     },
@@ -3870,7 +3870,7 @@ fn parse_alter_table() {
         AlterTableOperation::SetTblProperties { table_properties } => {
             assert_eq!(
                 table_properties,
-                [SqlOption {
+                [SqlOption::KeyValue {
                     name: Ident {
                         value: "classification".to_string(),
                         quote_style: Some('\'')
@@ -3958,11 +3958,11 @@ fn parse_alter_view_with_options() {
         Statement::AlterView { with_options, .. } => {
             assert_eq!(
                 vec![
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "foo".into(),
                         value: Expr::Value(Value::SingleQuotedString("bar".into())),
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "a".into(),
                         value: Expr::Value(number("123")),
                     },
@@ -6729,11 +6729,11 @@ fn parse_create_view_with_options() {
         Statement::CreateView { options, .. } => {
             assert_eq!(
                 CreateTableOptions::With(vec![
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "foo".into(),
                         value: Expr::Value(Value::SingleQuotedString("bar".into())),
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "a".into(),
                         value: Expr::Value(number("123")),
                     },
@@ -8827,11 +8827,11 @@ fn parse_cache_table() {
             table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]),
             has_as: false,
             options: vec![
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K1"),
                     value: Expr::Value(Value::SingleQuotedString("V1".into())),
                 },
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K2"),
                     value: Expr::Value(number("0.88")),
                 },
@@ -8852,11 +8852,11 @@ fn parse_cache_table() {
             table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]),
             has_as: false,
             options: vec![
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K1"),
                     value: Expr::Value(Value::SingleQuotedString("V1".into())),
                 },
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K2"),
                     value: Expr::Value(number("0.88")),
                 },
@@ -8877,11 +8877,11 @@ fn parse_cache_table() {
             table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]),
             has_as: true,
             options: vec![
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K1"),
                     value: Expr::Value(Value::SingleQuotedString("V1".into())),
                 },
-                SqlOption {
+                SqlOption::KeyValue {
                     name: Ident::with_quote('\'', "K2"),
                     value: Expr::Value(number("0.88")),
                 },
@@ -9695,7 +9695,7 @@ fn parse_unload() {
                 value: "s3://...".to_string(),
                 quote_style: Some('\'')
             },
-            with: vec![SqlOption {
+            with: vec![SqlOption::KeyValue {
                 name: Ident {
                     value: "format".to_string(),
                     quote_style: None
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 5c2ec87..b0a5546 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -268,6 +268,74 @@ fn parse_mssql_create_role() {
     }
 }
 
+#[test]
+fn parse_mssql_create_table_with_clustered_index_option() {
+    let sql = "CREATE TABLE t (id INT) WITH (CLUSTERED INDEX (id DESC))";
+    match ms().verified_stmt(sql) {
+        Statement::CreateTable(CreateTable { with_options, .. }) => {
+            assert_eq!(
+                with_options,
+                vec![SqlOption::ClusteredIndex(vec![OrderByExpr {
+                    expr: Expr::Identifier(Ident::new("id")),
+                    asc: Some(false),
+                    nulls_first: None,
+                    with_fill: None,
+                }])]
+            );
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_mssql_create_table_with_clustered_columnstore_index_option() {
+    let sql = "CREATE TABLE t (id INT) WITH (CLUSTERED COLUMNSTORE INDEX)";
+    match ms().verified_stmt(sql) {
+        Statement::CreateTable(CreateTable { with_options, .. }) => {
+            assert_eq!(with_options, vec![SqlOption::ClusteredColumnstoreIndex]);
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_mssql_create_table_with_heap_option() {
+    let sql = "CREATE TABLE t (id INT) WITH (HEAP)";
+    match ms().verified_stmt(sql) {
+        Statement::CreateTable(CreateTable { with_options, .. }) => {
+            assert_eq!(with_options, vec![SqlOption::Heap]);
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_mssql_create_table_with_partition_option() {
+    let sql = "CREATE TABLE t (id INT) WITH (PARTITION (id RANGE RIGHT FOR VALUES (10, 100)))";
+    match ms().verified_stmt(sql) {
+        Statement::CreateTable(CreateTable { with_options, .. }) => {
+            assert_eq!(
+                with_options,
+                vec![SqlOption::Partition {
+                    column: Ident::new("id"),
+                    range_direction: PartitionRangeDirection::Right,
+                    for_values: vec![
+                        Expr::Value(number("10")),
+                        Expr::Value(number("100")),
+                    ],
+                }]
+            );
+        }
+        _ => unreachable!(),
+    }
+}
+
+#[test]
+fn parse_mssql_create_table_with_mixed_with_options() {
+    let sql = "CREATE TABLE t (id INT) WITH (DISTRIBUTION = HASH(id), CLUSTERED COLUMNSTORE INDEX)";
+    let _ = ms().one_statement_parses_to(sql, sql);
+}
+
 #[test]
 fn parse_alter_role() {
     let sql = "ALTER ROLE old_name WITH NAME = new_name";
diff --git a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 1ebb5d5..bd39ddf 100644
--- a/tmp/agent-patch-flux-pr-1414.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -461,15 +461,15 @@ fn parse_create_table_with_defaults() {
             assert_eq!(
                 with_options,
                 vec![
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "fillfactor".into(),
                         value: Expr::Value(number("20"))
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "user_catalog_table".into(),
                         value: Expr::Value(Value::Boolean(true))
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "autovacuum_vacuum_threshold".into(),
                         value: Expr::Value(number("100"))
                     },
@@ -4482,11 +4482,11 @@ fn parse_create_table_with_options() {
         Statement::CreateTable(CreateTable { with_options, .. }) => {
             assert_eq!(
                 vec![
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "foo".into(),
                         value: Expr::Value(Value::SingleQuotedString("bar".into())),
                     },
-                    SqlOption {
+                    SqlOption::KeyValue {
                         name: "a".into(),
                         value: Expr::Value(number("123")),
                     },