diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 3192af8bb..961bbef79 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -600,6 +600,8 @@ pub enum ColumnOption { sequence_options: Option>, generation_expr: Option, generation_expr_mode: Option, + /// false if 'GENERATED ALWAYS' is skipped (option starts with AS) + generated_keyword: bool, }, } @@ -641,6 +643,7 @@ impl fmt::Display for ColumnOption { sequence_options, generation_expr, generation_expr_mode, + generated_keyword, } => { if let Some(expr) = generation_expr { let modifier = match generation_expr_mode { @@ -648,7 +651,11 @@ impl fmt::Display for ColumnOption { Some(GeneratedExpressionMode::Virtual) => " VIRTUAL", Some(GeneratedExpressionMode::Stored) => " STORED", }; - write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?; + if *generated_keyword { + write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?; + } else { + write!(f, "AS ({expr}){modifier}")?; + } Ok(()) } else { // Like Postgres - generated from sequence diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d9d4761c3..2817fa15d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4282,6 +4282,10 @@ impl<'a> Parser<'a> { Ok(Some(ColumnOption::OnUpdate(expr))) } else if self.parse_keyword(Keyword::GENERATED) { self.parse_optional_column_option_generated() + } else if self.parse_keyword(Keyword::AS) + && dialect_of!(self is MySqlDialect | SQLiteDialect | DuckDbDialect | GenericDialect) + { + self.parse_optional_column_option_as() } else { Ok(None) } @@ -4300,6 +4304,7 @@ impl<'a> Parser<'a> { sequence_options: Some(sequence_options), generation_expr: None, generation_expr_mode: None, + generated_keyword: true, })) } else if self.parse_keywords(&[ Keyword::BY, @@ -4317,6 +4322,7 @@ impl<'a> Parser<'a> { sequence_options: Some(sequence_options), generation_expr: None, generation_expr_mode: None, + generated_keyword: true, })) } else if self.parse_keywords(&[Keyword::ALWAYS, Keyword::AS]) { if self.expect_token(&Token::LParen).is_ok() { @@ -4341,6 +4347,7 @@ impl<'a> Parser<'a> { sequence_options: None, generation_expr: Some(expr), generation_expr_mode: expr_mode, + generated_keyword: true, })) } else { Ok(None) @@ -4350,6 +4357,32 @@ impl<'a> Parser<'a> { } } + fn parse_optional_column_option_as(&mut self) -> Result, ParserError> { + // Some DBs allow 'AS (expr)', shorthand for GENERATED ALWAYS AS + self.expect_token(&Token::LParen)?; + let expr = self.parse_expr()?; + self.expect_token(&Token::RParen)?; + + let (gen_as, expr_mode) = if self.parse_keywords(&[Keyword::STORED]) { + ( + GeneratedAs::ExpStored, + Some(GeneratedExpressionMode::Stored), + ) + } else if self.parse_keywords(&[Keyword::VIRTUAL]) { + (GeneratedAs::Always, Some(GeneratedExpressionMode::Virtual)) + } else { + (GeneratedAs::Always, None) + }; + + Ok(Some(ColumnOption::Generated { + generated_as: gen_as, + sequence_options: None, + generation_expr: Some(expr), + generation_expr_mode: expr_mode, + generated_keyword: false, + })) + } + pub fn parse_referential_action(&mut self) -> Result { if self.parse_keyword(Keyword::RESTRICT) { Ok(ReferentialAction::Restrict) diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 0036d52c3..70e3c9d2c 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -517,6 +517,10 @@ fn parse_create_table_gencol() { let sql_stored = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) STORED)"; mysql_and_generic().verified_stmt(sql_stored); + + mysql_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2))"); + mysql_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) VIRTUAL)"); + mysql_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) STORED)"); } #[test] diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index cc0d53b14..a3328290e 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -215,6 +215,10 @@ fn parse_create_table_gencol() { let sql_stored = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) STORED)"; sqlite_and_generic().verified_stmt(sql_stored); + + sqlite_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2))"); + sqlite_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) VIRTUAL)"); + sqlite_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) STORED)"); } #[test]