From a8a8e65b7c7dddaba79d1d1553aabd88ae9c2568 Mon Sep 17 00:00:00 2001 From: sam <2740878+sam-mmm@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:24:00 +0530 Subject: [PATCH] PostgreSQL: GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY and GENERATED ALWAYS AS ( generation_expr ) support (#832) * GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) basic impl - test are failing. * PostgreSQL GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) and GENERATED ALWAYS AS ( generation_expr ) STORED implementation. --- src/ast/ddl.rs | 64 +++++++++- src/ast/mod.rs | 2 +- src/dialect/ansi.rs | 7 +- src/dialect/bigquery.rs | 8 +- src/dialect/clickhouse.rs | 4 +- src/dialect/generic.rs | 12 +- src/dialect/hive.rs | 11 +- src/dialect/mssql.rs | 12 +- src/dialect/mysql.rs | 6 +- src/dialect/postgresql.rs | 8 +- src/dialect/snowflake.rs | 8 +- src/dialect/sqlite.rs | 6 +- src/keywords.rs | 2 + src/parser.rs | 49 ++++++++ src/tokenizer.rs | 2 +- tests/sqlparser_custom_dialect.rs | 8 +- tests/sqlparser_postgres.rs | 198 +++++++++++++++++++++++++++++- 17 files changed, 350 insertions(+), 57 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 3edabd7f..b515c261 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -24,7 +24,9 @@ use serde::{Deserialize, Serialize}; use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; -use crate::ast::{display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName}; +use crate::ast::{ + display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions, +}; use crate::tokenizer::Token; /// An `ALTER TABLE` (`Statement::AlterTable`) operation @@ -575,6 +577,13 @@ pub enum ColumnOption { CharacterSet(ObjectName), Comment(String), OnUpdate(Expr), + /// `Generated`s are modifiers that follow a column definition in a `CREATE + /// TABLE` statement. + Generated { + generated_as: GeneratedAs, + sequence_options: Option>, + generation_expr: Option, + }, } impl fmt::Display for ColumnOption { @@ -610,10 +619,63 @@ impl fmt::Display for ColumnOption { CharacterSet(n) => write!(f, "CHARACTER SET {n}"), Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)), OnUpdate(expr) => write!(f, "ON UPDATE {expr}"), + Generated { + generated_as, + sequence_options, + generation_expr, + } => match generated_as { + GeneratedAs::Always => { + write!(f, "GENERATED ALWAYS AS IDENTITY")?; + if sequence_options.is_some() { + let so = sequence_options.as_ref().unwrap(); + if !so.is_empty() { + write!(f, " (")?; + } + for sequence_option in so { + write!(f, "{sequence_option}")?; + } + if !so.is_empty() { + write!(f, " )")?; + } + } + Ok(()) + } + GeneratedAs::ByDefault => { + write!(f, "GENERATED BY DEFAULT AS IDENTITY")?; + if sequence_options.is_some() { + let so = sequence_options.as_ref().unwrap(); + if !so.is_empty() { + write!(f, " (")?; + } + for sequence_option in so { + write!(f, "{sequence_option}")?; + } + if !so.is_empty() { + write!(f, " )")?; + } + } + Ok(()) + } + GeneratedAs::ExpStored => { + let expr = generation_expr.as_ref().unwrap(); + write!(f, "GENERATED ALWAYS AS ({expr}) STORED") + } + }, } } } +/// `GeneratedAs`s are modifiers that follow a column option in a `generated`. +/// 'ExpStored' is PostgreSQL specific +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum GeneratedAs { + Always, + ByDefault, + ExpStored, +} + fn display_constraint_name(name: &'_ Option) -> impl fmt::Display + '_ { struct ConstraintName<'a>(&'a Option); impl<'a> fmt::Display for ConstraintName<'a> { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index bd31949e..c19466ce 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -30,7 +30,7 @@ pub use self::data_type::{ }; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, - ColumnOptionDef, IndexType, KeyOrIndexDisplay, ReferentialAction, TableConstraint, + ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ReferentialAction, TableConstraint, }; pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ diff --git a/src/dialect/ansi.rs b/src/dialect/ansi.rs index 1015ca2d..14c83ae1 100644 --- a/src/dialect/ansi.rs +++ b/src/dialect/ansi.rs @@ -17,13 +17,10 @@ pub struct AnsiDialect {} impl Dialect for AnsiDialect { fn is_identifier_start(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) - || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_' } } diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index e42676b2..8266a32f 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -22,13 +22,13 @@ impl Dialect for BigQueryDialect { } fn is_identifier_start(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '_' || ch == '-' } diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index 24ec5e49..395116f9 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -18,10 +18,10 @@ pub struct ClickHouseDialect {} impl Dialect for ClickHouseDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://clickhouse.com/docs/en/sql-reference/syntax/#syntax-identifiers - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || ('0'..='9').contains(&ch) + self.is_identifier_start(ch) || ch.is_ascii_digit() } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 818fa0d0..51ae3daf 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -17,17 +17,13 @@ pub struct GenericDialect; impl Dialect for GenericDialect { fn is_identifier_start(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ch == '_' - || ch == '#' - || ch == '@' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' || ch == '#' || ch == '@' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '@' || ch == '$' || ch == '#' diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index ceb5488e..96cefb1d 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -21,16 +21,13 @@ impl Dialect for HiveDialect { } fn is_identifier_start(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) - || ch == '$' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '$' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '_' || ch == '$' || ch == '{' diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 539a17a9..682376b1 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -23,17 +23,13 @@ impl Dialect for MsSqlDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-2017#rules-for-regular-identifiers // We don't support non-latin "letters" currently. - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ch == '_' - || ch == '#' - || ch == '@' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' || ch == '#' || ch == '@' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '@' || ch == '$' || ch == '#' diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index d6095262..441b2c24 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -20,8 +20,8 @@ impl Dialect for MySqlDialect { // See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html. // We don't yet support identifiers beginning with numbers, as that // makes it hard to distinguish numeric literals. - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() || ch == '_' || ch == '$' || ch == '@' @@ -29,7 +29,7 @@ impl Dialect for MySqlDialect { } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || ('0'..='9').contains(&ch) + self.is_identifier_start(ch) || ch.is_ascii_digit() } fn is_delimited_identifier_start(&self, ch: char) -> bool { diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index da07fefe..97b6b0ba 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -24,13 +24,13 @@ impl Dialect for PostgreSqlDialect { // See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS // We don't yet support identifiers beginning with "letters with // diacritical marks and non-Latin letters" - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '$' || ch == '_' } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 11108e97..4fc8de73 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -18,13 +18,13 @@ pub struct SnowflakeDialect; impl Dialect for SnowflakeDialect { // see https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html fn is_identifier_start(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '$' || ch == '_' } diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 64d7f62f..fa21224f 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -28,15 +28,15 @@ impl Dialect for SQLiteDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://www.sqlite.org/draft/tokenreq.html - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() || ch == '_' || ch == '$' || ('\u{007f}'..='\u{ffff}').contains(&ch) } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || ('0'..='9').contains(&ch) + self.is_identifier_start(ch) || ch.is_ascii_digit() } fn parse_statement(&self, parser: &mut Parser) -> Option> { diff --git a/src/keywords.rs b/src/keywords.rs index dd5ead7e..a2db1a35 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -77,6 +77,7 @@ define_keywords!( ALL, ALLOCATE, ALTER, + ALWAYS, ANALYZE, AND, ANTI, @@ -270,6 +271,7 @@ define_keywords!( FUNCTION, FUNCTIONS, FUSION, + GENERATED, GET, GLOBAL, GRANT, diff --git a/src/parser.rs b/src/parser.rs index da487e36..98fe589d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3567,6 +3567,55 @@ impl<'a> Parser<'a> { { let expr = self.parse_expr()?; Ok(Some(ColumnOption::OnUpdate(expr))) + } else if self.parse_keyword(Keyword::GENERATED) { + self.parse_optional_column_option_generated() + } else { + Ok(None) + } + } + fn parse_optional_column_option_generated( + &mut self, + ) -> Result, ParserError> { + if self.parse_keywords(&[Keyword::ALWAYS, Keyword::AS, Keyword::IDENTITY]) { + let mut sequence_options = vec![]; + if self.expect_token(&Token::LParen).is_ok() { + sequence_options = self.parse_create_sequence_options()?; + self.expect_token(&Token::RParen)?; + } + Ok(Some(ColumnOption::Generated { + generated_as: GeneratedAs::Always, + sequence_options: Some(sequence_options), + generation_expr: None, + })) + } else if self.parse_keywords(&[ + Keyword::BY, + Keyword::DEFAULT, + Keyword::AS, + Keyword::IDENTITY, + ]) { + let mut sequence_options = vec![]; + if self.expect_token(&Token::LParen).is_ok() { + sequence_options = self.parse_create_sequence_options()?; + self.expect_token(&Token::RParen)?; + } + Ok(Some(ColumnOption::Generated { + generated_as: GeneratedAs::ByDefault, + sequence_options: Some(sequence_options), + generation_expr: None, + })) + } else if self.parse_keywords(&[Keyword::ALWAYS, Keyword::AS]) { + if self.expect_token(&Token::LParen).is_ok() { + let expr = self.parse_expr()?; + self.expect_token(&Token::RParen)?; + let _ = self.parse_keywords(&[Keyword::STORED]); + Ok(Some(ColumnOption::Generated { + generated_as: GeneratedAs::ExpStored, + sequence_options: None, + generation_expr: Some(expr), + })) + } else { + Ok(None) + } } else { Ok(None) } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 8134947d..bc6e3add 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -596,7 +596,7 @@ impl<'a> Tokenizer<'a> { let word = self.tokenize_word(ch, chars); // TODO: implement parsing of exponent here - if word.chars().all(|x| ('0'..='9').contains(&x) || x == '.') { + if word.chars().all(|x| x.is_ascii_digit() || x == '.') { let mut inner_state = State { peekable: word.chars().peekable(), line: 0, diff --git a/tests/sqlparser_custom_dialect.rs b/tests/sqlparser_custom_dialect.rs index 465ce872..51659138 100644 --- a/tests/sqlparser_custom_dialect.rs +++ b/tests/sqlparser_custom_dialect.rs @@ -126,13 +126,13 @@ fn custom_statement_parser() -> Result<(), ParserError> { } fn is_identifier_start(ch: char) -> bool { - ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' + ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } fn is_identifier_part(ch: char) -> bool { - ('a'..='z').contains(&ch) - || ('A'..='Z').contains(&ch) - || ('0'..='9').contains(&ch) + ch.is_ascii_lowercase() + || ch.is_ascii_uppercase() + || ch.is_ascii_digit() || ch == '$' || ch == '_' } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index e541e515..359889aa 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -22,6 +22,202 @@ use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, PostgreSqlDialect}; use sqlparser::parser::ParserError; +#[test] +fn parse_create_table_generated_always_as_identity() { + //With primary key + let sql = "CREATE TABLE table2 ( + column21 bigint primary key generated always as identity , + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column21 BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column21 bigint primary key generated by default as identity , + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column21 BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, \ + column30 TEXT)", + ); + + //With out primary key + let sql = "CREATE TABLE table2 ( + column22 bigint generated always as identity , + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column22 BIGINT GENERATED ALWAYS AS IDENTITY, \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column22 bigint generated by default as identity , + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column22 BIGINT GENERATED BY DEFAULT AS IDENTITY, \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column23 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 NO CYCLE ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column23 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 NO CYCLE ), \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column24 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 CYCLE ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column24 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 CYCLE ), \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column25 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column25 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column26 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column26 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column27 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column27 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column28 bigint generated by default as identity ( INCREMENT 1 MINVALUE 1 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column28 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column29 bigint generated by default as identity ( INCREMENT 1 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column29 BIGINT GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 ), \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column22 bigint generated always as identity , + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column22 BIGINT GENERATED ALWAYS AS IDENTITY, \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column23 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 NO CYCLE ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column23 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 NO CYCLE ), \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column24 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 CYCLE ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column24 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 CYCLE ), \ + column30 TEXT)", + ); + + let sql = "CREATE TABLE table2 ( + column25 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column25 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 CACHE 2 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column26 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column26 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 START WITH 10 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column27 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column27 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 20 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column28 bigint generated always as identity ( INCREMENT 1 MINVALUE 1 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column28 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 MINVALUE 1 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + column29 bigint generated always as identity ( INCREMENT 1 ), + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + column29 BIGINT GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 ), \ + column30 TEXT)", + ); + let sql = "CREATE TABLE table2 ( + priceInDollar numeric, + princeInPound numeric GENERATED ALWAYS AS (priceInDollar * 0.22) STORED, + column30 text );"; + pg().one_statement_parses_to( + sql, + "CREATE TABLE table2 (\ + priceInDollar NUMERIC, \ + princeInPound NUMERIC GENERATED ALWAYS AS (priceInDollar * 0.22) STORED, \ + column30 TEXT)", + ); +} + #[test] fn parse_create_sequence() { // SimpleLogger::new().init().unwrap(); @@ -1408,12 +1604,10 @@ fn parse_pg_regex_match_ops() { fn parse_array_index_expr() { #[cfg(feature = "bigdecimal")] let num: Vec = (0..=10) - .into_iter() .map(|s| Expr::Value(Value::Number(bigdecimal::BigDecimal::from(s), false))) .collect(); #[cfg(not(feature = "bigdecimal"))] let num: Vec = (0..=10) - .into_iter() .map(|s| Expr::Value(Value::Number(s.to_string(), false))) .collect();