agent_patch
runs/2026-02-28__18-27-22__gpt-5-4/flux-pr-1747/flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/agent-logs/agent.patch
49948 bytes
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/dml.rs b/app/src/ast/dml.rs
index 9cdb1ca..0c9b942 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/dml.rs
+++ b/app/src/ast/dml.rs
@@ -33,11 +33,11 @@ pub use super::ddl::{ColumnDef, TableConstraint};
use super::{
display_comma_separated, display_separated, query::InputFormatClause, Assignment, ClusteredBy,
- CommentDef, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat,
- HiveRowFormat, Ident, IndexType, InsertAliases, MysqlInsertPriority, ObjectName, OnCommit,
- OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem, Setting,
- SqlOption, SqliteOnConflict, StorageSerializationPolicy, TableEngine, TableObject,
- TableWithJoins, Tag, WrappedCollection,
+ CommentDef, CreateTableOptions, Expr, FileFormat, FromTable, HiveDistributionStyle,
+ HiveFormat, HiveIOFormat, HiveRowFormat, Ident, IndexType, InsertAliases,
+ MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query,
+ RowAccessPolicy, SelectItem, Setting, SqlOption, SqliteOnConflict,
+ StorageSerializationPolicy, TableObject, TableWithJoins, Tag, WrappedCollection,
};
/// Index column type.
@@ -146,19 +146,13 @@ pub struct CreateTable {
pub constraints: Vec<TableConstraint>,
pub hive_distribution: HiveDistributionStyle,
pub hive_formats: Option<HiveFormat>,
- pub table_properties: Vec<SqlOption>,
- pub with_options: Vec<SqlOption>,
+ pub table_options: Vec<CreateTableOptions>,
pub file_format: Option<FileFormat>,
pub location: Option<String>,
pub query: Option<Box<Query>>,
pub without_rowid: bool,
pub like: Option<ObjectName>,
pub clone: Option<ObjectName>,
- pub engine: Option<TableEngine>,
- pub comment: Option<CommentDef>,
- pub auto_increment_offset: Option<u32>,
- pub default_charset: Option<String>,
- pub collation: Option<String>,
pub on_commit: Option<OnCommit>,
/// ClickHouse "ON CLUSTER" clause:
/// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
@@ -282,8 +276,14 @@ impl Display for CreateTable {
// Hive table comment should be after column definitions, please refer to:
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
- if let Some(CommentDef::AfterColumnDefsWithoutEq(comment)) = &self.comment {
- write!(f, " COMMENT '{comment}'")?;
+ for option_group in &self.table_options {
+ if let CreateTableOptions::Plain(options) = option_group {
+ for option in options {
+ if let SqlOption::Comment(CommentDef::AfterColumnDefsWithoutEq(_)) = option {
+ write!(f, " {option}")?;
+ }
+ }
+ }
}
// Only for SQLite
@@ -375,35 +375,19 @@ impl Display for CreateTable {
}
write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?;
}
- if !self.table_properties.is_empty() {
- write!(
- f,
- " TBLPROPERTIES ({})",
- display_comma_separated(&self.table_properties)
- )?;
- }
- if !self.with_options.is_empty() {
- write!(f, " WITH ({})", display_comma_separated(&self.with_options))?;
- }
- if let Some(engine) = &self.engine {
- write!(f, " ENGINE={engine}")?;
- }
- if let Some(comment_def) = &self.comment {
- match comment_def {
- CommentDef::WithEq(comment) => {
- write!(f, " COMMENT = '{comment}'")?;
- }
- CommentDef::WithoutEq(comment) => {
- write!(f, " COMMENT '{comment}'")?;
- }
- // For CommentDef::AfterColumnDefsWithoutEq will be displayed after column definition
- CommentDef::AfterColumnDefsWithoutEq(_) => (),
+ for option_group in &self.table_options {
+ let is_after_columns_comment = matches!(
+ option_group,
+ CreateTableOptions::Plain(options)
+ if options.iter().any(|option| matches!(
+ option,
+ SqlOption::Comment(CommentDef::AfterColumnDefsWithoutEq(_))
+ ))
+ );
+ if !is_after_columns_comment {
+ write!(f, " {option_group}")?;
}
}
-
- if let Some(auto_increment_offset) = self.auto_increment_offset {
- write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
- }
if let Some(primary_key) = &self.primary_key {
write!(f, " PRIMARY KEY {}", primary_key)?;
}
@@ -503,13 +487,6 @@ impl Display for CreateTable {
write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
}
- if let Some(default_charset) = &self.default_charset {
- write!(f, " DEFAULT CHARSET={default_charset}")?;
- }
- if let Some(collation) = &self.collation {
- write!(f, " COLLATE={collation}")?;
- }
-
if self.on_commit.is_some() {
let on_commit = match self.on_commit {
Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS",
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/helpers/stmt_create_table.rs b/app/src/ast/helpers/stmt_create_table.rs
index 1c50cb8..baf0410 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/helpers/stmt_create_table.rs
+++ b/app/src/ast/helpers/stmt_create_table.rs
@@ -26,9 +26,10 @@ use sqlparser_derive::{Visit, VisitMut};
use super::super::dml::CreateTable;
use crate::ast::{
- ClusteredBy, ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident,
- ObjectName, OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement,
- StorageSerializationPolicy, TableConstraint, TableEngine, Tag, WrappedCollection,
+ ClusteredBy, ColumnDef, CommentDef, CreateTableOptions, Expr, FileFormat,
+ HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
+ RowAccessPolicy, SqlOption, Statement, StorageSerializationPolicy, TableConstraint, Tag,
+ WrappedCollection,
};
use crate::parser::ParserError;
@@ -76,19 +77,13 @@ pub struct CreateTableBuilder {
pub constraints: Vec<TableConstraint>,
pub hive_distribution: HiveDistributionStyle,
pub hive_formats: Option<HiveFormat>,
- pub table_properties: Vec<SqlOption>,
- pub with_options: Vec<SqlOption>,
+ pub table_options: Vec<CreateTableOptions>,
pub file_format: Option<FileFormat>,
pub location: Option<String>,
pub query: Option<Box<Query>>,
pub without_rowid: bool,
pub like: Option<ObjectName>,
pub clone: Option<ObjectName>,
- pub engine: Option<TableEngine>,
- pub comment: Option<CommentDef>,
- pub auto_increment_offset: Option<u32>,
- pub default_charset: Option<String>,
- pub collation: Option<String>,
pub on_commit: Option<OnCommit>,
pub on_cluster: Option<Ident>,
pub primary_key: Option<Box<Expr>>,
@@ -131,19 +126,13 @@ impl CreateTableBuilder {
constraints: vec![],
hive_distribution: HiveDistributionStyle::NONE,
hive_formats: None,
- table_properties: vec![],
- with_options: vec![],
+ table_options: vec![],
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
- engine: None,
- comment: None,
- auto_increment_offset: None,
- default_charset: None,
- collation: None,
on_commit: None,
on_cluster: None,
primary_key: None,
@@ -230,13 +219,23 @@ impl CreateTableBuilder {
self
}
+ pub fn table_options(mut self, table_options: Vec<CreateTableOptions>) -> Self {
+ self.table_options = table_options;
+ self
+ }
+
pub fn table_properties(mut self, table_properties: Vec<SqlOption>) -> Self {
- self.table_properties = table_properties;
+ if !table_properties.is_empty() {
+ self.table_options
+ .push(CreateTableOptions::TableProperties(table_properties));
+ }
self
}
pub fn with_options(mut self, with_options: Vec<SqlOption>) -> Self {
- self.with_options = with_options;
+ if !with_options.is_empty() {
+ self.table_options.push(CreateTableOptions::With(with_options));
+ }
self
}
pub fn file_format(mut self, file_format: Option<FileFormat>) -> Self {
@@ -268,28 +267,11 @@ impl CreateTableBuilder {
self
}
- pub fn engine(mut self, engine: Option<TableEngine>) -> Self {
- self.engine = engine;
- self
- }
-
pub fn comment(mut self, comment: Option<CommentDef>) -> Self {
- self.comment = comment;
- self
- }
-
- pub fn auto_increment_offset(mut self, offset: Option<u32>) -> Self {
- self.auto_increment_offset = offset;
- self
- }
-
- pub fn default_charset(mut self, default_charset: Option<String>) -> Self {
- self.default_charset = default_charset;
- self
- }
-
- pub fn collation(mut self, collation: Option<String>) -> Self {
- self.collation = collation;
+ if let Some(comment) = comment {
+ self.table_options
+ .push(CreateTableOptions::Plain(vec![SqlOption::Comment(comment)]));
+ }
self
}
@@ -437,19 +419,13 @@ impl CreateTableBuilder {
constraints: self.constraints,
hive_distribution: self.hive_distribution,
hive_formats: self.hive_formats,
- table_properties: self.table_properties,
- with_options: self.with_options,
+ table_options: self.table_options,
file_format: self.file_format,
location: self.location,
query: self.query,
without_rowid: self.without_rowid,
like: self.like,
clone: self.clone,
- engine: self.engine,
- comment: self.comment,
- auto_increment_offset: self.auto_increment_offset,
- default_charset: self.default_charset,
- collation: self.collation,
on_commit: self.on_commit,
on_cluster: self.on_cluster,
primary_key: self.primary_key,
@@ -499,19 +475,13 @@ impl TryFrom<Statement> for CreateTableBuilder {
constraints,
hive_distribution,
hive_formats,
- table_properties,
- with_options,
+ table_options,
file_format,
location,
query,
without_rowid,
like,
clone,
- engine,
- comment,
- auto_increment_offset,
- default_charset,
- collation,
on_commit,
on_cluster,
primary_key,
@@ -548,19 +518,13 @@ impl TryFrom<Statement> for CreateTableBuilder {
constraints,
hive_distribution,
hive_formats,
- table_properties,
- with_options,
+ table_options,
file_format,
location,
query,
without_rowid,
like,
clone,
- engine,
- comment,
- auto_increment_offset,
- default_charset,
- collation,
on_commit,
on_cluster,
primary_key,
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 582922a..0c43adf 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -2662,6 +2662,12 @@ impl fmt::Display for Declare {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum CreateTableOptions {
None,
+ /// Plain table options specified as space-separated clauses.
+ /// e.g. `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
+ Plain(Vec<SqlOption>),
+ /// Hive table properties.
+ /// e.g. `TBLPROPERTIES ("prop" = "value")`
+ TableProperties(Vec<SqlOption>),
/// Options specified using the `WITH` keyword.
/// e.g. `WITH (description = "123")`
///
@@ -2686,6 +2692,12 @@ pub enum CreateTableOptions {
impl fmt::Display for CreateTableOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
+ CreateTableOptions::Plain(options) => {
+ write!(f, "{}", display_separated(options, " "))
+ }
+ CreateTableOptions::TableProperties(options) => {
+ write!(f, "TBLPROPERTIES ({})", display_comma_separated(options))
+ }
CreateTableOptions::With(with_options) => {
write!(f, "WITH ({})", display_comma_separated(with_options))
}
@@ -7560,6 +7572,18 @@ pub enum SqlOption {
range_direction: Option<PartitionRangeDirection>,
for_values: Vec<Expr>,
},
+ /// `ENGINE=<engine_name>(...)`
+ Engine(TableEngine),
+ /// `AUTO_INCREMENT <value>`
+ AutoIncrement(u32),
+ /// `DEFAULT CHARSET=<charset>`
+ DefaultCharset(ObjectName),
+ /// `COLLATE=<collation>`
+ Collate(ObjectName),
+ /// `TABLESPACE[=]<name>`
+ TableSpace(ObjectName),
+ /// `COMMENT ['='] '<value>'`
+ Comment(CommentDef),
}
impl fmt::Display for SqlOption {
@@ -7591,6 +7615,19 @@ impl fmt::Display for SqlOption {
display_comma_separated(for_values)
)
}
+ SqlOption::Engine(engine) => write!(f, "ENGINE={engine}"),
+ SqlOption::AutoIncrement(value) => write!(f, "AUTO_INCREMENT {value}"),
+ SqlOption::DefaultCharset(charset) => write!(f, "DEFAULT CHARSET={charset}"),
+ SqlOption::Collate(collation) => write!(f, "COLLATE={collation}"),
+ SqlOption::TableSpace(tablespace) => write!(f, "TABLESPACE={tablespace}"),
+ SqlOption::Comment(comment) => match comment {
+ CommentDef::WithEq(value) => {
+ write!(f, "COMMENT = '{}'", value::escape_single_quote_string(value))
+ }
+ CommentDef::WithoutEq(value) | CommentDef::AfterColumnDefsWithoutEq(value) => {
+ write!(f, "COMMENT '{}'", value::escape_single_quote_string(value))
+ }
+ },
}
}
}
@@ -8868,7 +8905,7 @@ impl Display for CreateViewParams {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct TableEngine {
pub name: String,
- pub parameters: Option<Vec<Ident>>,
+ pub parameters: Option<Vec<Expr>>,
}
impl Display for TableEngine {
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs b/app/src/ast/spans.rs
index 836f229..cf9bcce 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/ast/spans.rs
+++ b/app/src/ast/spans.rs
@@ -567,19 +567,13 @@ impl Spanned for CreateTable {
constraints,
hive_distribution: _, // hive specific
hive_formats: _, // hive specific
- table_properties,
- with_options,
+ table_options,
file_format: _, // enum
location: _, // string, no span
query,
without_rowid: _, // bool
like,
clone,
- engine: _, // todo
- comment: _, // todo, no span
- auto_increment_offset: _, // u32, no span
- default_charset: _, // string, no span
- collation: _, // string, no span
on_commit: _, // enum
on_cluster: _, // todo, clickhouse specific
primary_key: _, // todo, clickhouse specific
@@ -610,8 +604,7 @@ impl Spanned for CreateTable {
core::iter::once(name.span())
.chain(columns.iter().map(|i| i.span()))
.chain(constraints.iter().map(|i| i.span()))
- .chain(table_properties.iter().map(|i| i.span()))
- .chain(with_options.iter().map(|i| i.span()))
+ .chain(table_options.iter().map(|i| i.span()))
.chain(query.iter().map(|i| i.span()))
.chain(like.iter().map(|i| i.span()))
.chain(clone.iter().map(|i| i.span())),
@@ -1004,6 +997,17 @@ impl Spanned for SqlOption {
} => union_spans(
core::iter::once(column_name.span).chain(for_values.iter().map(|i| i.span())),
),
+ SqlOption::Engine(table_engine) => union_spans(
+ table_engine
+ .parameters
+ .iter()
+ .flat_map(|parameters| parameters.iter().map(|expr| expr.span())),
+ ),
+ SqlOption::AutoIncrement(_) => Span::empty(),
+ SqlOption::DefaultCharset(object_name)
+ | SqlOption::Collate(object_name)
+ | SqlOption::TableSpace(object_name) => object_name.span(),
+ SqlOption::Comment(_) => Span::empty(),
}
}
}
@@ -1039,6 +1043,8 @@ impl Spanned for CreateTableOptions {
fn span(&self) -> Span {
match self {
CreateTableOptions::None => Span::empty(),
+ CreateTableOptions::Plain(vec) => union_spans(vec.iter().map(|i| i.span())),
+ CreateTableOptions::TableProperties(vec) => union_spans(vec.iter().map(|i| i.span())),
CreateTableOptions::With(vec) => union_spans(vec.iter().map(|i| i.span())),
CreateTableOptions::Options(vec) => union_spans(vec.iter().map(|i| i.span())),
}
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/keywords.rs b/app/src/keywords.rs
index bf8a191..8688552 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/keywords.rs
+++ b/app/src/keywords.rs
@@ -870,6 +870,7 @@ define_keywords!(
TABLE,
TABLES,
TABLESAMPLE,
+ TABLESPACE,
TAG,
TARGET,
TASK,
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs b/app/src/parser/mod.rs
index cbd464c..5651a9c 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -7041,17 +7041,19 @@ impl<'a> Parser<'a> {
// parse optional column list (schema)
let (columns, constraints) = self.parse_columns()?;
- let mut comment = if dialect_of!(self is HiveDialect)
- && self.parse_keyword(Keyword::COMMENT)
- {
+ let mut table_options = vec![];
+ if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::COMMENT) {
let next_token = self.next_token();
match next_token.token {
- Token::SingleQuotedString(str) => Some(CommentDef::AfterColumnDefsWithoutEq(str)),
+ Token::SingleQuotedString(str) => Self::push_create_table_option(
+ &mut table_options,
+ CreateTableOptions::Plain(vec![SqlOption::Comment(
+ CommentDef::AfterColumnDefsWithoutEq(str),
+ )]),
+ ),
_ => self.expected("comment", next_token)?,
}
- } else {
- None
- };
+ }
// SQLite supports `WITHOUT ROWID` at the end of `CREATE TABLE`
let without_rowid = self.parse_keywords(&[Keyword::WITHOUT, Keyword::ROWID]);
@@ -7059,39 +7061,7 @@ impl<'a> Parser<'a> {
let hive_distribution = self.parse_hive_distribution()?;
let clustered_by = self.parse_optional_clustered_by()?;
let hive_formats = self.parse_hive_formats()?;
- // PostgreSQL supports `WITH ( options )`, before `AS`
- let with_options = self.parse_options(Keyword::WITH)?;
- let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?;
-
- let engine = if self.parse_keyword(Keyword::ENGINE) {
- self.expect_token(&Token::Eq)?;
- let next_token = self.next_token();
- match next_token.token {
- Token::Word(w) => {
- let name = w.value;
- let parameters = if self.peek_token() == Token::LParen {
- Some(self.parse_parenthesized_identifiers()?)
- } else {
- None
- };
- Some(TableEngine { name, parameters })
- }
- _ => self.expected("identifier", next_token)?,
- }
- } else {
- None
- };
-
- let auto_increment_offset = if self.parse_keyword(Keyword::AUTO_INCREMENT) {
- let _ = self.consume_token(&Token::Eq);
- let next_token = self.next_token();
- match next_token.token {
- Token::Number(s, _) => Some(Self::parse::<u32>(s, next_token.span.start)?),
- _ => self.expected("literal int", next_token)?,
- }
- } else {
- None
- };
+ self.parse_create_table_option_groups(&mut table_options)?;
// ClickHouse supports `PRIMARY KEY`, before `ORDER BY`
// https://clickhouse.com/docs/en/sql-reference/statements/create/table#primary-key
@@ -7119,29 +7089,11 @@ impl<'a> Parser<'a> {
None
};
- let create_table_config = self.parse_optional_create_table_config()?;
+ self.parse_create_table_option_groups(&mut table_options)?;
- let default_charset = if self.parse_keywords(&[Keyword::DEFAULT, Keyword::CHARSET]) {
- self.expect_token(&Token::Eq)?;
- let next_token = self.next_token();
- match next_token.token {
- Token::Word(w) => Some(w.value),
- _ => self.expected("identifier", next_token)?,
- }
- } else {
- None
- };
+ let create_table_config = self.parse_optional_create_table_config()?;
- let collation = if self.parse_keywords(&[Keyword::COLLATE]) {
- self.expect_token(&Token::Eq)?;
- let next_token = self.next_token();
- match next_token.token {
- Token::Word(w) => Some(w.value),
- _ => self.expected("identifier", next_token)?,
- }
- } else {
- None
- };
+ self.parse_create_table_option_groups(&mut table_options)?;
let on_commit = if self.parse_keywords(&[Keyword::ON, Keyword::COMMIT]) {
Some(self.parse_create_table_on_commit()?)
@@ -7151,13 +7103,6 @@ impl<'a> Parser<'a> {
let strict = self.parse_keyword(Keyword::STRICT);
- // Excludes Hive dialect here since it has been handled after table column definitions.
- if !dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::COMMENT) {
- // rewind the COMMENT keyword
- self.prev_token();
- comment = self.parse_optional_inline_comment()?
- };
-
// Parse optional `AS ( query )`
let query = if self.parse_keyword(Keyword::AS) {
Some(self.parse_query()?)
@@ -7174,8 +7119,7 @@ impl<'a> Parser<'a> {
.temporary(temporary)
.columns(columns)
.constraints(constraints)
- .with_options(with_options)
- .table_properties(table_properties)
+ .table_options(table_options)
.or_replace(or_replace)
.if_not_exists(if_not_exists)
.transient(transient)
@@ -7186,12 +7130,7 @@ impl<'a> Parser<'a> {
.without_rowid(without_rowid)
.like(like)
.clone_clause(clone)
- .engine(engine)
- .comment(comment)
- .auto_increment_offset(auto_increment_offset)
.order_by(order_by)
- .default_charset(default_charset)
- .collation(collation)
.on_commit(on_commit)
.on_cluster(on_cluster)
.clustered_by(clustered_by)
@@ -7219,6 +7158,101 @@ impl<'a> Parser<'a> {
}
}
+ fn push_create_table_option(
+ table_options: &mut Vec<CreateTableOptions>,
+ option_group: CreateTableOptions,
+ ) {
+ table_options.push(option_group);
+ }
+
+ fn parse_create_table_option_groups(
+ &mut self,
+ table_options: &mut Vec<CreateTableOptions>,
+ ) -> Result<(), ParserError> {
+ while let Some(option_group) = self.maybe_parse_create_table_option_group()? {
+ Self::push_create_table_option(table_options, option_group);
+ }
+ Ok(())
+ }
+
+ fn maybe_parse_create_table_option_group(
+ &mut self,
+ ) -> Result<Option<CreateTableOptions>, ParserError> {
+ let with_options = self.parse_options(Keyword::WITH)?;
+ if !with_options.is_empty() {
+ return Ok(Some(CreateTableOptions::With(with_options)));
+ }
+
+ let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?;
+ if !table_properties.is_empty() {
+ return Ok(Some(CreateTableOptions::TableProperties(table_properties)));
+ }
+
+ let mut options = vec![];
+ loop {
+ let option = if self.parse_keyword(Keyword::ENGINE) {
+ self.expect_token(&Token::Eq)?;
+ let next_token = self.next_token();
+ match next_token.token {
+ Token::Word(w) => {
+ let name = w.value;
+ let parameters = if self.consume_token(&Token::LParen) {
+ let parameters = if self.peek_token() != Token::RParen {
+ self.parse_comma_separated(|p| p.parse_expr())?
+ } else {
+ vec![]
+ };
+ self.expect_token(&Token::RParen)?;
+ Some(parameters)
+ } else {
+ None
+ };
+ Some(SqlOption::Engine(TableEngine { name, parameters }))
+ }
+ _ => self.expected("identifier", next_token)?,
+ }
+ } else if self.parse_keyword(Keyword::AUTO_INCREMENT) {
+ let _ = self.consume_token(&Token::Eq);
+ let next_token = self.next_token();
+ match next_token.token {
+ Token::Number(s, _) => Some(SqlOption::AutoIncrement(Self::parse::<u32>(
+ s,
+ next_token.span.start,
+ )?)),
+ _ => self.expected("literal int", next_token)?,
+ }
+ } else if self.parse_keywords(&[Keyword::DEFAULT, Keyword::CHARSET])
+ || self.parse_keyword(Keyword::CHARSET)
+ {
+ self.expect_token(&Token::Eq)?;
+ Some(SqlOption::DefaultCharset(self.parse_object_name(false)?))
+ } else if self.parse_keyword(Keyword::COLLATE) {
+ self.expect_token(&Token::Eq)?;
+ Some(SqlOption::Collate(self.parse_object_name(false)?))
+ } else if self.parse_keyword(Keyword::TABLESPACE) {
+ let _ = self.consume_token(&Token::Eq);
+ Some(SqlOption::TableSpace(self.parse_object_name(false)?))
+ } else if !dialect_of!(self is HiveDialect) {
+ self.parse_optional_inline_comment()?
+ .map(SqlOption::Comment)
+ } else {
+ None
+ };
+
+ if let Some(option) = option {
+ options.push(option);
+ } else {
+ break;
+ }
+ }
+
+ if options.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(CreateTableOptions::Plain(options)))
+ }
+ }
+
/// Parse configuration like inheritance, partitioning, clustering information during the table creation.
///
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_2)
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_clickhouse.rs b/app/tests/sqlparser_clickhouse.rs
index c56f988..1bbac2b 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_clickhouse.rs
+++ b/app/tests/sqlparser_clickhouse.rs
@@ -721,7 +721,7 @@ fn parse_create_table_with_primary_key() {
Statement::CreateTable(CreateTable {
name,
columns,
- engine,
+ table_options,
primary_key,
order_by,
..
@@ -743,14 +743,18 @@ fn parse_create_table_with_primary_key() {
columns
);
assert_eq!(
- engine,
- Some(TableEngine {
- name: "SharedMergeTree".to_string(),
- parameters: Some(vec![
- Ident::with_quote('\'', "/clickhouse/tables/{uuid}/{shard}"),
- Ident::with_quote('\'', "{replica}"),
- ]),
- })
+ table_options,
+ vec![CreateTableOptions::Plain(vec![SqlOption::Engine(
+ TableEngine {
+ name: "SharedMergeTree".to_string(),
+ parameters: Some(vec![
+ Expr::Value(Value::SingleQuotedString(
+ "/clickhouse/tables/{uuid}/{shard}".to_string()
+ )),
+ Expr::Value(Value::SingleQuotedString("{replica}".to_string())),
+ ]),
+ }
+ )])]
);
fn assert_function(actual: &Function, name: &str, arg: &str) -> bool {
assert_eq!(actual.name, ObjectName::from(vec![Ident::new(name)]));
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs b/app/tests/sqlparser_common.rs
index 1ddf3f9..3b5132e 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_common.rs
+++ b/app/tests/sqlparser_common.rs
@@ -3657,13 +3657,16 @@ fn parse_create_table() {
name,
columns,
constraints,
- with_options,
+ table_options,
if_not_exists: false,
external: false,
file_format: None,
location: None,
..
}) => {
+ let [CreateTableOptions::With(with_options)] = table_options.as_slice() else {
+ panic!("expected WITH table options");
+ };
assert_eq!("uk_cities", name.to_string());
assert_eq!(
columns,
@@ -3840,13 +3843,16 @@ fn parse_create_table_with_constraint_characteristics() {
name,
columns,
constraints,
- with_options,
+ table_options,
if_not_exists: false,
external: false,
file_format: None,
location: None,
..
}) => {
+ let [CreateTableOptions::With(with_options)] = table_options.as_slice() else {
+ panic!("expected WITH table options");
+ };
assert_eq!("uk_cities", name.to_string());
assert_eq!(
columns,
@@ -4421,7 +4427,10 @@ fn parse_create_table_with_options() {
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
match generic.verified_stmt(sql) {
- Statement::CreateTable(CreateTable { with_options, .. }) => {
+ Statement::CreateTable(CreateTable { table_options, .. }) => {
+ let [CreateTableOptions::With(with_options)] = table_options.as_slice() else {
+ panic!("expected WITH table options");
+ };
assert_eq!(
vec
match all_dialects_except(|d| d.is::<HiveDialect>()).verified_stmt(sql) {
Statement::CreateTable(CreateTable {
- columns, comment, ..
+ columns,
+ table_options,
+ ..
}) => {
assert_eq!(
columns,
@@ -11434,8 +11447,10 @@ fn test_parse_inline_comment() {
}]
);
assert_eq!(
- comment.unwrap(),
- CommentDef::WithEq("comment with equal".to_string())
+ table_options,
+ vec![CreateTableOptions::Plain(vec![SqlOption::Comment(
+ CommentDef::WithEq("comment with equal".to_string())
+ )])]
);
}
_ => unreachable!(),
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_duckdb.rs b/app/tests/sqlparser_duckdb.rs
index 3205832..48eb0ef 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_duckdb.rs
+++ b/app/tests/sqlparser_duckdb.rs
@@ -735,19 +735,13 @@ fn test_duckdb_union_datatype() {
storage: Default::default(),
location: Default::default()
}),
- table_properties: Default::default(),
- with_options: Default::default(),
+ table_options: Default::default(),
file_format: Default::default(),
location: Default::default(),
query: Default::default(),
without_rowid: Default::default(),
like: Default::default(),
clone: Default::default(),
- engine: Default::default(),
- comment: Default::default(),
- auto_increment_offset: Default::default(),
- default_charset: Default::default(),
- collation: Default::default(),
on_commit: Default::default(),
on_cluster: Default::default(),
primary_key: Default::default(),
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_hive.rs b/app/tests/sqlparser_hive.rs
index 9b04309..8b01152 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_hive.rs
+++ b/app/tests/sqlparser_hive.rs
@@ -130,12 +130,12 @@ fn create_table_with_comment() {
" INTO 4 BUCKETS"
);
match hive().verified_stmt(sql) {
- Statement::CreateTable(CreateTable { comment, .. }) => {
+ Statement::CreateTable(CreateTable { table_options, .. }) => {
assert_eq!(
- comment,
- Some(CommentDef::AfterColumnDefsWithoutEq(
- "table comment".to_string()
- ))
+ table_options,
+ vec![CreateTableOptions::Plain(vec![SqlOption::Comment(
+ CommentDef::AfterColumnDefsWithoutEq("table comment".to_string())
+ )])]
)
}
_ => unreachable!(),
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs b/app/tests/sqlparser_mssql.rs
index 88e7a1f..96eaecd 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mssql.rs
+++ b/app/tests/sqlparser_mssql.rs
@@ -1747,19 +1747,13 @@ fn parse_create_table_with_valid_options() {
storage: None,
location: None,
},),
- table_properties: vec![],
- with_options,
+ table_options: vec![CreateTableOptions::With(with_options)],
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
- engine: None,
- comment: None,
- auto_increment_offset: None,
- default_charset: None,
- collation: None,
on_commit: None,
on_cluster: None,
primary_key: None,
@@ -1918,19 +1912,13 @@ fn parse_create_table_with_identity_column() {
storage: None,
location: None,
},),
- table_properties: vec![],
- with_options: vec![],
+ table_options: vec![],
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
- engine: None,
- comment: None,
- auto_increment_offset: None,
- default_charset: None,
- collation: None,
on_commit: None,
on_cluster: None,
primary_key: None,
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs b/app/tests/sqlparser_mysql.rs
index 990107b..07d60c9 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_mysql.rs
+++ b/app/tests/sqlparser_mysql.rs
@@ -848,9 +848,22 @@ fn parse_create_table_comment() {
for sql in [without_equal, with_equal] {
match mysql().verified_stmt(sql) {
- Statement::CreateTable(CreateTable { name, comment, .. }) => {
+ Statement::CreateTable(CreateTable {
+ name,
+ table_options,
+ ..
+ }) => {
assert_eq!(name.to_string(), "foo");
- assert_eq!(comment.expect("Should exist").to_string(), "baz");
+ assert!(table_options.iter().any(|group| matches!(
+ group,
+ CreateTableOptions::Plain(options)
+ if options.iter().any(|option| matches!(
+ option,
+ SqlOption::Comment(CommentDef::WithoutEq(comment))
+ | SqlOption::Comment(CommentDef::WithEq(comment))
+ if comment == "baz"
+ ))
+ )));
}
_ => unreachable!(),
}
@@ -868,14 +881,18 @@ fn parse_create_table_auto_increment_offset() {
match mysql().one_statement_parses_to(sql, canonical) {
Statement::CreateTable(CreateTable {
name,
- auto_increment_offset,
+ table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
- assert_eq!(
- auto_increment_offset.expect("Should exist").to_string(),
- "123"
- );
+ assert!(table_options.iter().any(|group| matches!(
+ group,
+ CreateTableOptions::Plain(options)
+ if options.iter().any(|option| matches!(
+ option,
+ SqlOption::AutoIncrement(value) if *value == 123
+ ))
+ )));
}
_ => unreachable!(),
}
@@ -921,8 +938,7 @@ fn parse_create_table_engine_default_charset() {
Statement::CreateTable(CreateTable {
name,
columns,
- engine,
- default_charset,
+ table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
@@ -935,13 +951,15 @@ fn parse_create_table_engine_default_charset() {
columns
);
assert_eq!(
- engine,
- Some(TableEngine {
- name: "InnoDB".to_string(),
- parameters: None
- })
+ table_options,
+ vec![CreateTableOptions::Plain(vec![
+ SqlOption::Engine(TableEngine {
+ name: "InnoDB".to_string(),
+ parameters: None
+ }),
+ SqlOption::DefaultCharset(ObjectName::from(vec![Ident::new("utf8mb3")])),
+ ])]
);
- assert_eq!(default_charset, Some("utf8mb3".to_string()));
}
_ => unreachable!(),
}
@@ -954,7 +972,7 @@ fn parse_create_table_collate() {
Statement::CreateTable(CreateTable {
name,
columns,
- collation,
+ table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
@@ -966,7 +984,12 @@ fn parse_create_table_collate() {
},],
columns
);
- assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
+ assert_eq!(
+ table_options,
+ vec![CreateTableOptions::Plain(vec![SqlOption::Collate(
+ ObjectName::from(vec![Ident::new("utf8mb4_0900_ai_ci")])
+ )])]
+ );
}
_ => unreachable!(),
}
@@ -978,12 +1001,24 @@ fn parse_create_table_both_options_and_as_query() {
match mysql_and_generic().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
- collation,
+ table_options,
query,
..
}) => {
assert_eq!(name.to_string(), "foo");
- assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
+ assert_eq!(
+ table_options,
+ vec![CreateTableOptions::Plain(vec![
+ SqlOption::Engine(TableEngine {
+ name: "InnoDB".to_string(),
+ parameters: None
+ }),
+ SqlOption::DefaultCharset(ObjectName::from(vec![Ident::new("utf8mb3")])),
+ SqlOption::Collate(ObjectName::from(vec![Ident::new(
+ "utf8mb4_0900_ai_ci"
+ )])),
+ ])]
+ );
assert_eq!(
query.unwrap().body.as_select().unwrap().projection,
vec![SelectItem::UnnamedExpr(Expr::Value(
@@ -1001,6 +1036,29 @@ fn parse_create_table_both_options_and_as_query() {
));
}
+#[test]
+fn parse_create_table_plain_options_in_any_order() {
+ let sql = "CREATE TABLE foo (id INT(11)) DEFAULT CHARSET=utf8mb3 ENGINE=InnoDB COLLATE=utf8mb4_0900_ai_ci";
+ match mysql().one_statement_parses_to(sql, sql) {
+ Statement::CreateTable(CreateTable { table_options, .. }) => {
+ assert_eq!(
+ table_options,
+ vec![CreateTableOptions::Plain(vec![
+ SqlOption::DefaultCharset(ObjectName::from(vec![Ident::new("utf8mb3")])),
+ SqlOption::Engine(TableEngine {
+ name: "InnoDB".to_string(),
+ parameters: None
+ }),
+ SqlOption::Collate(ObjectName::from(vec![Ident::new(
+ "utf8mb4_0900_ai_ci"
+ )])),
+ ])]
+ );
+ }
+ _ => unreachable!(),
+ }
+}
+
#[test]
fn parse_create_table_comment_character_set() {
let sql = "CREATE TABLE foo (s TEXT CHARACTER SET utf8mb4 COMMENT 'comment')";
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs b/app/tests/sqlparser_postgres.rs
index 6c008c8..b385c1c 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_postgres.rs
+++ b/app/tests/sqlparser_postgres.rs
@@ -348,7 +348,7 @@ fn parse_create_table_with_defaults() {
name,
columns,
constraints,
- with_options,
+ table_options,
if_not_exists: false,
external: false,
file_format: None,
@@ -356,6 +356,9 @@ fn parse_create_table_with_defaults() {
..
}) => {
use pretty_assertions::assert_eq;
+ let [CreateTableOptions::With(with_options)] = table_options.as_slice() else {
+ panic!("expected WITH table options");
+ };
assert_eq!("public.customer", name.to_string());
assert_eq!(
columns,
@@ -4668,13 +4671,14 @@ fn parse_create_table_with_alias() {
name,
columns,
constraints,
- with_options: _with_options,
+ table_options,
if_not_exists: false,
external: false,
file_format: None,
location: None,
..
}) => {
+ assert_eq!(table_options, vec![CreateTableOptions::With(_with_options.clone())]);
assert_eq!("public.datatype_aliases", name.to_string());
assert_eq!(
columns,
@@ -5078,7 +5082,10 @@ fn parse_at_time_zone() {
fn parse_create_table_with_options() {
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
match pg().verified_stmt(sql) {
- Statement::CreateTable(CreateTable { with_options, .. }) => {
+ Statement::CreateTable(CreateTable { table_options, .. }) => {
+ let [CreateTableOptions::With(with_options)] = table_options.as_slice() else {
+ panic!("expected WITH table options");
+ };
assert_eq!(
vec![
SqlOption::KeyValue {
@@ -5506,19 +5513,13 @@ fn parse_trigger_related_functions() {
storage: None,
location: None
}),
- table_properties: vec![],
- with_options: vec![],
+ table_options: vec![],
file_format: None,
location: None,
query: None,
without_rowid: false,
like: None,
clone: None,
- engine: None,
- comment: None,
- auto_increment_offset: None,
- default_charset: None,
- collation: None,
on_commit: None,
on_cluster: None,
primary_key: None,
diff --git a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_snowflake.rs b/app/tests/sqlparser_snowflake.rs
index aa97411..cd9f18a 100644
--- a/tmp/agent-patch-flux-pr-1747.1-of-1.2026-02-28__18-27-22__gpt-5-4/app/tests/sqlparser_snowflake.rs
+++ b/app/tests/sqlparser_snowflake.rs
@@ -470,9 +470,18 @@ fn test_snowflake_create_table_cluster_by() {
#[test]
fn test_snowflake_create_table_comment() {
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) COMMENT = 'some comment'") {
- Statement::CreateTable(CreateTable { name, comment, .. }) => {
+ Statement::CreateTable(CreateTable {
+ name,
+ table_options,
+ ..
+ }) => {
assert_eq!("my_table", name.to_string());
- assert_eq!("some comment", comment.unwrap().to_string());
+ assert_eq!(
+ table_options,
+ vec![CreateTableOptions::Plain(vec![SqlOption::Comment(
+ CommentDef::WithEq("some comment".to_string())
+ )])]
+ );
}
_ => unreachable!(),
}