Skip to content

Commit 6d69a43

Browse files
committed
Preserve presence/absence of VIRTUAL for generated columns
1 parent f0d0f5b commit 6d69a43

File tree

6 files changed

+53
-38
lines changed

6 files changed

+53
-38
lines changed

src/ast/ddl.rs

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ pub enum ColumnOption {
599599
generated_as: GeneratedAs,
600600
sequence_options: Option<Vec<SequenceOptions>>,
601601
generation_expr: Option<Expr>,
602+
generation_expr_mode: Option<GeneratedExpressionMode>,
602603
},
603604
}
604605

@@ -639,31 +640,25 @@ impl fmt::Display for ColumnOption {
639640
generated_as,
640641
sequence_options,
641642
generation_expr,
642-
} => match generated_as {
643-
GeneratedAs::Always => {
644-
if let Some(expr) = generation_expr {
645-
// Like SQLite, MySQL - expr evaluated on read
646-
write!(f, "GENERATED ALWAYS AS ({expr}) VIRTUAL")
647-
} else {
648-
// Like Postgres - generated from sequence
649-
write!(f, "GENERATED ALWAYS AS IDENTITY")?;
650-
if sequence_options.is_some() {
651-
let so = sequence_options.as_ref().unwrap();
652-
if !so.is_empty() {
653-
write!(f, " (")?;
654-
}
655-
for sequence_option in so {
656-
write!(f, "{sequence_option}")?;
657-
}
658-
if !so.is_empty() {
659-
write!(f, " )")?;
660-
}
661-
}
662-
Ok(())
663-
}
664-
}
665-
GeneratedAs::ByDefault => {
666-
write!(f, "GENERATED BY DEFAULT AS IDENTITY")?;
643+
generation_expr_mode,
644+
} => {
645+
if let Some(expr) = generation_expr {
646+
let modifier = match generation_expr_mode {
647+
None => "",
648+
Some(GeneratedExpressionMode::Virtual) => " VIRTUAL",
649+
Some(GeneratedExpressionMode::Stored) => " STORED",
650+
};
651+
write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?;
652+
Ok(())
653+
} else {
654+
// Like Postgres - generated from sequence
655+
let when = match generated_as {
656+
GeneratedAs::Always => "ALWAYS",
657+
GeneratedAs::ByDefault => "BY DEFAULT",
658+
// ExpStored goes with an expression, handled above
659+
GeneratedAs::ExpStored => unreachable!(),
660+
};
661+
write!(f, "GENERATED {when} AS IDENTITY")?;
667662
if sequence_options.is_some() {
668663
let so = sequence_options.as_ref().unwrap();
669664
if !so.is_empty() {
@@ -678,11 +673,7 @@ impl fmt::Display for ColumnOption {
678673
}
679674
Ok(())
680675
}
681-
GeneratedAs::ExpStored => {
682-
let expr = generation_expr.as_ref().unwrap();
683-
write!(f, "GENERATED ALWAYS AS ({expr}) STORED")
684-
}
685-
},
676+
}
686677
}
687678
}
688679
}
@@ -698,6 +689,16 @@ pub enum GeneratedAs {
698689
ExpStored,
699690
}
700691

692+
/// `GeneratedExpressionMode`s are modifiers that follow an expression in a `generated`.
693+
/// No modifier is typically the same as Virtual.
694+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
695+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
696+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
697+
pub enum GeneratedExpressionMode {
698+
Virtual,
699+
Stored,
700+
}
701+
701702
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
702703
struct ConstraintName<'a>(&'a Option<Ident>);
703704
impl<'a> fmt::Display for ConstraintName<'a> {

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ pub use self::data_type::{
3131
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue};
3232
pub use self::ddl::{
3333
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
34-
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
35-
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
34+
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
35+
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
3636
UserDefinedTypeRepresentation,
3737
};
3838
pub use self::operator::{BinaryOperator, UnaryOperator};

src/parser/mod.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4231,6 +4231,7 @@ impl<'a> Parser<'a> {
42314231
generated_as: GeneratedAs::Always,
42324232
sequence_options: Some(sequence_options),
42334233
generation_expr: None,
4234+
generation_expr_mode: None,
42344235
}))
42354236
} else if self.parse_keywords(&[
42364237
Keyword::BY,
@@ -4247,25 +4248,31 @@ impl<'a> Parser<'a> {
42474248
generated_as: GeneratedAs::ByDefault,
42484249
sequence_options: Some(sequence_options),
42494250
generation_expr: None,
4251+
generation_expr_mode: None,
42504252
}))
42514253
} else if self.parse_keywords(&[Keyword::ALWAYS, Keyword::AS]) {
42524254
if self.expect_token(&Token::LParen).is_ok() {
42534255
let expr = self.parse_expr()?;
42544256
self.expect_token(&Token::RParen)?;
4255-
let gen_as = if self.parse_keywords(&[Keyword::STORED]) {
4256-
Ok(GeneratedAs::ExpStored)
4257+
let (gen_as, expr_mode) = if self.parse_keywords(&[Keyword::STORED]) {
4258+
Ok((
4259+
GeneratedAs::ExpStored,
4260+
Some(GeneratedExpressionMode::Stored),
4261+
))
42574262
} else if dialect_of!(self is PostgreSqlDialect) {
42584263
// Postgres' AS IDENTITY branches are above, this one needs STORED
42594264
self.expected("STORED", self.peek_token())
4265+
} else if self.parse_keywords(&[Keyword::VIRTUAL]) {
4266+
Ok((GeneratedAs::Always, Some(GeneratedExpressionMode::Virtual)))
42604267
} else {
4261-
let _ = self.parse_keywords(&[Keyword::VIRTUAL]);
4262-
Ok(GeneratedAs::Always)
4268+
Ok((GeneratedAs::Always, None))
42634269
}?;
42644270

42654271
Ok(Some(ColumnOption::Generated {
42664272
generated_as: gen_as,
42674273
sequence_options: None,
42684274
generation_expr: Some(expr),
4275+
generation_expr_mode: expr_mode,
42694276
}))
42704277
} else {
42714278
Ok(None)

src/test_utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ impl TestedDialects {
120120

121121
let only_statement = statements.pop().unwrap();
122122
if !canonical.is_empty() {
123+
println!("Canonical: {canonical}");
124+
println!("Reformed: {only_statement}");
123125
assert_eq!(canonical, only_statement.to_string())
124126
}
125127
only_statement

tests/sqlparser_mysql.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//! is also tested (on the inputs it can handle).
1616
1717
use matches::assert_matches;
18+
use sqlparser;
1819
use sqlparser::ast::Expr;
1920
use sqlparser::ast::Value;
2021
use sqlparser::ast::*;
@@ -510,8 +511,10 @@ fn parse_create_table_comment_character_set() {
510511
#[test]
511512
fn parse_create_table_gencol() {
512513
let sql_default = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2))";
514+
mysql_and_generic().verified_stmt(sql_default);
515+
513516
let sql_virt = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) VIRTUAL)";
514-
mysql_and_generic().one_statement_parses_to(sql_default, sql_virt);
517+
mysql_and_generic().verified_stmt(sql_virt);
515518

516519
let sql_stored = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) STORED)";
517520
mysql_and_generic().verified_stmt(sql_stored);

tests/sqlparser_sqlite.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,10 @@ fn parse_create_sqlite_quote() {
208208
#[test]
209209
fn parse_create_table_gencol() {
210210
let sql_default = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2))";
211+
sqlite_and_generic().verified_stmt(sql_default);
212+
211213
let sql_virt = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) VIRTUAL)";
212-
sqlite_and_generic().one_statement_parses_to(sql_default, sql_virt);
214+
sqlite_and_generic().verified_stmt(sql_virt);
213215

214216
let sql_stored = "CREATE TABLE t1 (a INT, b INT GENERATED ALWAYS AS (a * 2) STORED)";
215217
sqlite_and_generic().verified_stmt(sql_stored);

0 commit comments

Comments
 (0)