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
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")),
},