mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
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.
This commit is contained in:
parent
4ff3aeb040
commit
a8a8e65b7c
17 changed files with 350 additions and 57 deletions
|
@ -24,7 +24,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlparser_derive::{Visit, VisitMut};
|
use sqlparser_derive::{Visit, VisitMut};
|
||||||
|
|
||||||
use crate::ast::value::escape_single_quote_string;
|
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;
|
use crate::tokenizer::Token;
|
||||||
|
|
||||||
/// An `ALTER TABLE` (`Statement::AlterTable`) operation
|
/// An `ALTER TABLE` (`Statement::AlterTable`) operation
|
||||||
|
@ -575,6 +577,13 @@ pub enum ColumnOption {
|
||||||
CharacterSet(ObjectName),
|
CharacterSet(ObjectName),
|
||||||
Comment(String),
|
Comment(String),
|
||||||
OnUpdate(Expr),
|
OnUpdate(Expr),
|
||||||
|
/// `Generated`s are modifiers that follow a column definition in a `CREATE
|
||||||
|
/// TABLE` statement.
|
||||||
|
Generated {
|
||||||
|
generated_as: GeneratedAs,
|
||||||
|
sequence_options: Option<Vec<SequenceOptions>>,
|
||||||
|
generation_expr: Option<Expr>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ColumnOption {
|
impl fmt::Display for ColumnOption {
|
||||||
|
@ -610,10 +619,63 @@ impl fmt::Display for ColumnOption {
|
||||||
CharacterSet(n) => write!(f, "CHARACTER SET {n}"),
|
CharacterSet(n) => write!(f, "CHARACTER SET {n}"),
|
||||||
Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)),
|
Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)),
|
||||||
OnUpdate(expr) => write!(f, "ON UPDATE {expr}"),
|
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<Ident>) -> impl fmt::Display + '_ {
|
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
|
||||||
struct ConstraintName<'a>(&'a Option<Ident>);
|
struct ConstraintName<'a>(&'a Option<Ident>);
|
||||||
impl<'a> fmt::Display for ConstraintName<'a> {
|
impl<'a> fmt::Display for ConstraintName<'a> {
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub use self::data_type::{
|
||||||
};
|
};
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
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::operator::{BinaryOperator, UnaryOperator};
|
||||||
pub use self::query::{
|
pub use self::query::{
|
||||||
|
|
|
@ -17,13 +17,10 @@ pub struct AnsiDialect {}
|
||||||
|
|
||||||
impl Dialect for AnsiDialect {
|
impl Dialect for AnsiDialect {
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
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 {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_'
|
||||||
|| ('A'..='Z').contains(&ch)
|
|
||||||
|| ('0'..='9').contains(&ch)
|
|
||||||
|| ch == '_'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,13 @@ impl Dialect for BigQueryDialect {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
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 {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
|| ch == '-'
|
|| ch == '-'
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ pub struct ClickHouseDialect {}
|
||||||
impl Dialect for ClickHouseDialect {
|
impl Dialect for ClickHouseDialect {
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
fn is_identifier_start(&self, ch: char) -> bool {
|
||||||
// See https://clickhouse.com/docs/en/sql-reference/syntax/#syntax-identifiers
|
// 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 {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,13 @@ pub struct GenericDialect;
|
||||||
|
|
||||||
impl Dialect for GenericDialect {
|
impl Dialect for GenericDialect {
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
fn is_identifier_start(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' || ch == '#' || ch == '@'
|
||||||
|| ('A'..='Z').contains(&ch)
|
|
||||||
|| ch == '_'
|
|
||||||
|| ch == '#'
|
|
||||||
|| ch == '@'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_part(&self, ch: char) -> bool {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '@'
|
|| ch == '@'
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '#'
|
|| ch == '#'
|
||||||
|
|
|
@ -21,16 +21,13 @@ impl Dialect for HiveDialect {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
fn is_identifier_start(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '$'
|
||||||
|| ('A'..='Z').contains(&ch)
|
|
||||||
|| ('0'..='9').contains(&ch)
|
|
||||||
|| ch == '$'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_part(&self, ch: char) -> bool {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '{'
|
|| ch == '{'
|
||||||
|
|
|
@ -23,17 +23,13 @@ impl Dialect for MsSqlDialect {
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
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
|
// 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.
|
// We don't support non-latin "letters" currently.
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' || ch == '#' || ch == '@'
|
||||||
|| ('A'..='Z').contains(&ch)
|
|
||||||
|| ch == '_'
|
|
||||||
|| ch == '#'
|
|
||||||
|| ch == '@'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_part(&self, ch: char) -> bool {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '@'
|
|| ch == '@'
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '#'
|
|| ch == '#'
|
||||||
|
|
|
@ -20,8 +20,8 @@ impl Dialect for MySqlDialect {
|
||||||
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
|
// See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html.
|
||||||
// We don't yet support identifiers beginning with numbers, as that
|
// We don't yet support identifiers beginning with numbers, as that
|
||||||
// makes it hard to distinguish numeric literals.
|
// makes it hard to distinguish numeric literals.
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '@'
|
|| ch == '@'
|
||||||
|
@ -29,7 +29,7 @@ impl Dialect for MySqlDialect {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_part(&self, ch: char) -> bool {
|
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 {
|
fn is_delimited_identifier_start(&self, ch: char) -> bool {
|
||||||
|
|
|
@ -24,13 +24,13 @@ impl Dialect for PostgreSqlDialect {
|
||||||
// See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
// See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||||
// We don't yet support identifiers beginning with "letters with
|
// We don't yet support identifiers beginning with "letters with
|
||||||
// diacritical marks and non-Latin letters"
|
// 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 {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,13 @@ pub struct SnowflakeDialect;
|
||||||
impl Dialect for SnowflakeDialect {
|
impl Dialect for SnowflakeDialect {
|
||||||
// see https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html
|
// see https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
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 {
|
fn is_identifier_part(&self, ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,15 +28,15 @@ impl Dialect for SQLiteDialect {
|
||||||
|
|
||||||
fn is_identifier_start(&self, ch: char) -> bool {
|
fn is_identifier_start(&self, ch: char) -> bool {
|
||||||
// See https://www.sqlite.org/draft/tokenreq.html
|
// See https://www.sqlite.org/draft/tokenreq.html
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ('\u{007f}'..='\u{ffff}').contains(&ch)
|
|| ('\u{007f}'..='\u{ffff}').contains(&ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_part(&self, ch: char) -> bool {
|
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<Result<Statement, ParserError>> {
|
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||||
|
|
|
@ -77,6 +77,7 @@ define_keywords!(
|
||||||
ALL,
|
ALL,
|
||||||
ALLOCATE,
|
ALLOCATE,
|
||||||
ALTER,
|
ALTER,
|
||||||
|
ALWAYS,
|
||||||
ANALYZE,
|
ANALYZE,
|
||||||
AND,
|
AND,
|
||||||
ANTI,
|
ANTI,
|
||||||
|
@ -270,6 +271,7 @@ define_keywords!(
|
||||||
FUNCTION,
|
FUNCTION,
|
||||||
FUNCTIONS,
|
FUNCTIONS,
|
||||||
FUSION,
|
FUSION,
|
||||||
|
GENERATED,
|
||||||
GET,
|
GET,
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
GRANT,
|
GRANT,
|
||||||
|
|
|
@ -3567,6 +3567,55 @@ impl<'a> Parser<'a> {
|
||||||
{
|
{
|
||||||
let expr = self.parse_expr()?;
|
let expr = self.parse_expr()?;
|
||||||
Ok(Some(ColumnOption::OnUpdate(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<Option<ColumnOption>, 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 {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -596,7 +596,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
let word = self.tokenize_word(ch, chars);
|
let word = self.tokenize_word(ch, chars);
|
||||||
|
|
||||||
// TODO: implement parsing of exponent here
|
// 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 {
|
let mut inner_state = State {
|
||||||
peekable: word.chars().peekable(),
|
peekable: word.chars().peekable(),
|
||||||
line: 0,
|
line: 0,
|
||||||
|
|
|
@ -126,13 +126,13 @@ fn custom_statement_parser() -> Result<(), ParserError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_start(ch: char) -> bool {
|
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 {
|
fn is_identifier_part(ch: char) -> bool {
|
||||||
('a'..='z').contains(&ch)
|
ch.is_ascii_lowercase()
|
||||||
|| ('A'..='Z').contains(&ch)
|
|| ch.is_ascii_uppercase()
|
||||||
|| ('0'..='9').contains(&ch)
|
|| ch.is_ascii_digit()
|
||||||
|| ch == '$'
|
|| ch == '$'
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,202 @@ use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect};
|
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect};
|
||||||
use sqlparser::parser::ParserError;
|
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]
|
#[test]
|
||||||
fn parse_create_sequence() {
|
fn parse_create_sequence() {
|
||||||
// SimpleLogger::new().init().unwrap();
|
// SimpleLogger::new().init().unwrap();
|
||||||
|
@ -1408,12 +1604,10 @@ fn parse_pg_regex_match_ops() {
|
||||||
fn parse_array_index_expr() {
|
fn parse_array_index_expr() {
|
||||||
#[cfg(feature = "bigdecimal")]
|
#[cfg(feature = "bigdecimal")]
|
||||||
let num: Vec<Expr> = (0..=10)
|
let num: Vec<Expr> = (0..=10)
|
||||||
.into_iter()
|
|
||||||
.map(|s| Expr::Value(Value::Number(bigdecimal::BigDecimal::from(s), false)))
|
.map(|s| Expr::Value(Value::Number(bigdecimal::BigDecimal::from(s), false)))
|
||||||
.collect();
|
.collect();
|
||||||
#[cfg(not(feature = "bigdecimal"))]
|
#[cfg(not(feature = "bigdecimal"))]
|
||||||
let num: Vec<Expr> = (0..=10)
|
let num: Vec<Expr> = (0..=10)
|
||||||
.into_iter()
|
|
||||||
.map(|s| Expr::Value(Value::Number(s.to_string(), false)))
|
.map(|s| Expr::Value(Value::Number(s.to_string(), false)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue