Skip to content

Add support for generated columns skipping 'GENERATED ALWAYS' keywords #1058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,8 @@ pub enum ColumnOption {
sequence_options: Option<Vec<SequenceOptions>>,
generation_expr: Option<Expr>,
generation_expr_mode: Option<GeneratedExpressionMode>,
/// false if 'GENERATED ALWAYS' is skipped (option starts with AS)
generated_kw: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal preference, but I would be inclined to expand the name to generated_keyword for clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I've done that renaming.

},
}

Expand Down Expand Up @@ -641,14 +643,19 @@ impl fmt::Display for ColumnOption {
sequence_options,
generation_expr,
generation_expr_mode,
generated_kw,
} => {
if let Some(expr) = generation_expr {
let modifier = match generation_expr_mode {
None => "",
Some(GeneratedExpressionMode::Virtual) => " VIRTUAL",
Some(GeneratedExpressionMode::Stored) => " STORED",
};
write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?;
if *generated_kw {
write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?;
} else {
write!(f, "AS ({expr}){modifier}")?;
}
Ok(())
} else {
// Like Postgres - generated from sequence
Expand Down
33 changes: 33 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -4300,6 +4304,7 @@ impl<'a> Parser<'a> {
sequence_options: Some(sequence_options),
generation_expr: None,
generation_expr_mode: None,
generated_kw: true,
}))
} else if self.parse_keywords(&[
Keyword::BY,
Expand All @@ -4317,6 +4322,7 @@ impl<'a> Parser<'a> {
sequence_options: Some(sequence_options),
generation_expr: None,
generation_expr_mode: None,
generated_kw: true,
}))
} else if self.parse_keywords(&[Keyword::ALWAYS, Keyword::AS]) {
if self.expect_token(&Token::LParen).is_ok() {
Expand All @@ -4341,6 +4347,7 @@ impl<'a> Parser<'a> {
sequence_options: None,
generation_expr: Some(expr),
generation_expr_mode: expr_mode,
generated_kw: true,
}))
} else {
Ok(None)
Expand All @@ -4350,6 +4357,32 @@ impl<'a> Parser<'a> {
}
}

fn parse_optional_column_option_as(&mut self) -> Result<Option<ColumnOption>, 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_kw: false,
}))
}

pub fn parse_referential_action(&mut self) -> Result<ReferentialAction, ParserError> {
if self.parse_keyword(Keyword::RESTRICT) {
Ok(ReferentialAction::Restrict)
Expand Down
4 changes: 4 additions & 0 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down