diff --git a/src/ast/mod.rs b/src/ast/mod.rs index df6d95c41..5d6fea9bd 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4255,6 +4255,15 @@ pub enum Statement { show_options: ShowStatementOptions, }, /// ```sql + /// SHOW SEQUENCES [ LIKE '' ] [ IN { ACCOUNT | DATABASE | SCHEMA } ] + /// ``` + /// Snowflake-specific statement. + /// + ShowSequences { + /// Additional options for filtering and scoping the sequence listing. + show_options: ShowStatementOptions, + }, + /// ```sql /// SHOW VIEWS /// ``` ShowViews { @@ -4720,6 +4729,18 @@ pub enum Statement { name: ObjectName, }, /// ```sql + /// DESC[RIBE] SEQUENCE + /// ``` + /// Snowflake-specific statement. + /// + DescribeSequence { + /// `DESC | DESCRIBE` spelling used by the input statement. + describe_alias: DescribeAlias, + /// Sequence name. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + name: ObjectName, + }, + /// ```sql /// [EXPLAIN | DESC | DESCRIBE] /// ``` Explain { @@ -4803,6 +4824,10 @@ pub enum Statement { /// ``` /// Define a new sequence: CreateSequence { + /// `OR REPLACE` flag. + or_replace: bool, + /// `OR ALTER` flag. + or_alter: bool, /// Whether the sequence is temporary. temporary: bool, /// `IF NOT EXISTS` flag. @@ -4816,6 +4841,19 @@ pub enum Statement { /// Optional `OWNED BY` target. owned_by: Option, }, + /// ```sql + /// ALTER SEQUENCE [ IF EXISTS ] { RENAME TO | SET ... | UNSET COMMENT } + /// ``` + /// Snowflake-specific statement. + AlterSequence { + /// `IF EXISTS` flag. + if_exists: bool, + /// Sequence name. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + name: ObjectName, + /// Sequence alteration to perform. + operation: AlterSequenceOperation, + }, /// A `CREATE DOMAIN` statement. CreateDomain(CreateDomain), /// ```sql @@ -5224,6 +5262,10 @@ impl fmt::Display for Statement { describe_alias, name, } => write!(f, "{describe_alias} FILE FORMAT {name}"), + Statement::DescribeSequence { + describe_alias, + name, + } => write!(f, "{describe_alias} SEQUENCE {name}"), Statement::Explain { describe_alias, verbose, @@ -6041,6 +6083,9 @@ impl fmt::Display for Statement { Statement::ShowFileFormats { show_options } => { write!(f, "SHOW FILE FORMATS{show_options}") } + Statement::ShowSequences { show_options } => { + write!(f, "SHOW SEQUENCES{show_options}") + } Statement::ShowViews { terse, materialized, @@ -6297,6 +6342,8 @@ impl fmt::Display for Statement { } } Statement::CreateSequence { + or_replace, + or_alter, temporary, if_not_exists, name, @@ -6313,7 +6360,9 @@ impl fmt::Display for Statement { }; write!( f, - "CREATE {temporary}SEQUENCE {if_not_exists}{name}{as_type}", + "CREATE {or_replace}{or_alter}{temporary}SEQUENCE {if_not_exists}{name}{as_type}", + or_replace = if *or_replace { "OR REPLACE " } else { "" }, + or_alter = if *or_alter { "OR ALTER " } else { "" }, if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, temporary = if *temporary { "TEMPORARY " } else { "" }, name = name, @@ -6327,6 +6376,30 @@ impl fmt::Display for Statement { } write!(f, "") } + Statement::AlterSequence { + if_exists, + name, + operation, + } => { + write!( + f, + "ALTER SEQUENCE {if_exists}{name}", + if_exists = if *if_exists { "IF EXISTS " } else { "" }, + )?; + match operation { + AlterSequenceOperation::RenameTo { new_name } => { + write!(f, " RENAME TO {new_name}") + } + AlterSequenceOperation::SetOptions(options) => { + f.write_str(" SET")?; + for option in options { + write!(f, "{option}")?; + } + Ok(()) + } + AlterSequenceOperation::UnsetComment => f.write_str(" UNSET COMMENT"), + } + } Statement::CreateStage { or_replace, temporary, @@ -6682,6 +6755,10 @@ pub enum SequenceOptions { Cache(Expr), /// `CYCLE` or `NO CYCLE` option. Cycle(bool), + /// Snowflake `ORDER` or `NOORDER` option. + Order(bool), + /// Snowflake sequence comment. + Comment(String), } impl fmt::Display for SequenceOptions { @@ -6721,10 +6798,37 @@ impl fmt::Display for SequenceOptions { SequenceOptions::Cycle(no) => { write!(f, " {}CYCLE", if *no { "NO " } else { "" }) } + SequenceOptions::Order(ordered) => { + f.write_str(if *ordered { " ORDER" } else { " NOORDER" }) + } + SequenceOptions::Comment(comment) => { + write!( + f, + " COMMENT = '{}'", + value::escape_single_quote_string(comment) + ) + } } } } +/// An operation supported by Snowflake's `ALTER SEQUENCE` statement. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterSequenceOperation { + /// Rename a sequence. + RenameTo { + /// New sequence name. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + new_name: ObjectName, + }, + /// Set one or more sequence properties. + SetOptions(Vec), + /// Remove the sequence comment. + UnsetComment, +} + /// Assignment for a `SET` statement (name [=|TO] value) #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 07c93b364..91f547053 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -451,6 +451,7 @@ impl Spanned for Statement { Statement::ShowTables { .. } => Span::empty(), Statement::ShowStages { .. } => Span::empty(), Statement::ShowFileFormats { .. } => Span::empty(), + Statement::ShowSequences { .. } => Span::empty(), Statement::ShowCollation { .. } => Span::empty(), Statement::ShowCharset { .. } => Span::empty(), Statement::Use(u) => u.span(), @@ -470,6 +471,7 @@ impl Spanned for Statement { Statement::CreateStage { .. } => Span::empty(), Statement::AlterStage { .. } => Span::empty(), Statement::AlterFileFormat { .. } => Span::empty(), + Statement::AlterSequence { .. } => Span::empty(), Statement::CreateFileFormat { .. } => Span::empty(), Statement::Assert { .. } => Span::empty(), Statement::Grant { .. } => Span::empty(), @@ -482,6 +484,7 @@ impl Spanned for Statement { Statement::ExplainTable { .. } => Span::empty(), Statement::DescribeStage { .. } => Span::empty(), Statement::DescribeFileFormat { .. } => Span::empty(), + Statement::DescribeSequence { .. } => Span::empty(), Statement::Explain { .. } => Span::empty(), Statement::Savepoint { .. } => Span::empty(), Statement::ReleaseSavepoint { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 272a57040..1d5261440 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5189,6 +5189,12 @@ impl<'a> Parser<'a> { self.parse_create_secret(or_replace, temporary, persistent) } else if self.parse_keyword(Keyword::USER) { self.parse_create_user(or_replace).map(Into::into) + } else if self.parse_keyword(Keyword::SEQUENCE) { + if (or_replace || or_alter) && !dialect_of!(self is SnowflakeDialect) { + self.expected_ref("supported object type", self.peek_token_ref()) + } else { + self.parse_create_sequence(or_replace, or_alter, temporary) + } } else if or_replace { self.expected_ref( "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE", @@ -5208,8 +5214,6 @@ impl<'a> Parser<'a> { self.parse_create_database() } else if self.parse_keyword(Keyword::ROLE) { self.parse_create_role().map(Into::into) - } else if self.parse_keyword(Keyword::SEQUENCE) { - self.parse_create_sequence(temporary) } else if self.parse_keyword(Keyword::COLLATION) { self.parse_create_collation().map(Into::into) } else if self.parse_keyword(Keyword::TYPE) { @@ -10750,6 +10754,7 @@ impl<'a> Parser<'a> { Keyword::CONNECTOR, Keyword::ICEBERG, Keyword::SCHEMA, + Keyword::SEQUENCE, Keyword::USER, Keyword::OPERATOR, ])?; @@ -10799,14 +10804,51 @@ impl<'a> Parser<'a> { Keyword::ROLE => self.parse_alter_role(), Keyword::POLICY => self.parse_alter_policy().map(Into::into), Keyword::CONNECTOR => self.parse_alter_connector(), + Keyword::SEQUENCE => { + if dialect_of!(self is SnowflakeDialect) { + self.parse_alter_sequence() + } else { + self.expected_ref("supported ALTER object type", self.peek_token_ref()) + } + } Keyword::USER => self.parse_alter_user().map(Into::into), // unreachable because expect_one_of_keywords used above unexpected_keyword => Err(ParserError::ParserError( - format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"), + format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, SEQUENCE, USER, OPERATOR}}, got {unexpected_keyword:?}"), )), } } + fn parse_alter_sequence(&mut self) -> Result { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_object_name(false)?; + let operation = if self.parse_keyword(Keyword::RENAME) { + self.expect_keyword(Keyword::TO)?; + AlterSequenceOperation::RenameTo { + new_name: self.parse_object_name(false)?, + } + } else if self.parse_keyword(Keyword::SET) { + let options = self.parse_snowflake_sequence_options(false)?; + if options.is_empty() { + return self.expected_ref( + "INCREMENT, ORDER, NOORDER, or COMMENT after ALTER SEQUENCE SET", + self.peek_token_ref(), + ); + } + AlterSequenceOperation::SetOptions(options) + } else if self.parse_keywords(&[Keyword::UNSET, Keyword::COMMENT]) { + AlterSequenceOperation::UnsetComment + } else { + return self.expected_ref("RENAME, SET, or UNSET", self.peek_token_ref()); + }; + + Ok(Statement::AlterSequence { + if_exists, + name, + operation, + }) + } + fn parse_alter_aggregate_signature( &mut self, ) -> Result<(FunctionDesc, bool, Option>), ParserError> { @@ -14044,6 +14086,16 @@ impl<'a> Parser<'a> { }); } + if describe_alias != DescribeAlias::Explain + && dialect_of!(self is SnowflakeDialect) + && self.parse_keyword(Keyword::SEQUENCE) + { + return Ok(Statement::DescribeSequence { + describe_alias, + name: self.parse_object_name(false)?, + }); + } + match self.maybe_parse(|parser| parser.parse_statement())? { Some(Statement::Explain { .. }) | Some(Statement::ExplainTable { .. }) => Err( ParserError::ParserError("Explain must be root of the plan".to_string()), @@ -15566,6 +15618,16 @@ impl<'a> Parser<'a> { show_options: self.parse_show_stmt_options()?, }) } + } else if dialect_of!(self is SnowflakeDialect) && self.parse_keyword(Keyword::SEQUENCES) { + if terse || extended || full || session || global || external { + Err(ParserError::ParserError( + "SHOW SEQUENCES does not support SHOW modifiers".to_string(), + )) + } else { + Ok(Statement::ShowSequences { + show_options: self.parse_show_stmt_options()?, + }) + } } else if self.parse_keywords(&[Keyword::MATERIALIZED, Keyword::VIEWS]) { Ok(self.parse_show_views(terse, true)?) } else if self.parse_keyword(Keyword::VIEWS) { @@ -19872,7 +19934,12 @@ impl<'a> Parser<'a> { /// ``` /// /// See [Postgres docs](https://www.postgresql.org/docs/current/sql-createsequence.html) for more details. - pub fn parse_create_sequence(&mut self, temporary: bool) -> Result { + pub fn parse_create_sequence( + &mut self, + or_replace: bool, + or_alter: bool, + temporary: bool, + ) -> Result { //[ IF NOT EXISTS ] let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); //name @@ -19882,7 +19949,14 @@ impl<'a> Parser<'a> { if self.parse_keywords(&[Keyword::AS]) { data_type = Some(self.parse_data_type()?) } - let sequence_options = self.parse_create_sequence_options()?; + if dialect_of!(self is SnowflakeDialect) { + let _ = self.parse_keyword(Keyword::WITH); + } + let sequence_options = if dialect_of!(self is SnowflakeDialect) { + self.parse_snowflake_sequence_options(true)? + } else { + self.parse_create_sequence_options()? + }; // [ OWNED BY { table_name.column_name | NONE } ] let owned_by = if self.parse_keywords(&[Keyword::OWNED, Keyword::BY]) { if self.parse_keywords(&[Keyword::NONE]) { @@ -19894,6 +19968,8 @@ impl<'a> Parser<'a> { None }; Ok(Statement::CreateSequence { + or_replace, + or_alter, temporary, if_not_exists, name, @@ -19903,6 +19979,52 @@ impl<'a> Parser<'a> { }) } + fn parse_snowflake_sequence_options( + &mut self, + allow_start: bool, + ) -> Result, ParserError> { + let mut sequence_options = vec![]; + loop { + let option = if allow_start && self.parse_keyword(Keyword::START) { + let with = self.parse_keyword(Keyword::WITH); + let _ = self.consume_token(&Token::Eq); + Some(SequenceOptions::StartWith(self.parse_number()?, with)) + } else if self.parse_keyword(Keyword::INCREMENT) { + let by = self.parse_keyword(Keyword::BY); + let _ = self.consume_token(&Token::Eq); + Some(SequenceOptions::IncrementBy(self.parse_number()?, by)) + } else if self.parse_keyword(Keyword::ORDER) { + Some(SequenceOptions::Order(true)) + } else if self.parse_keyword(Keyword::NOORDER) { + Some(SequenceOptions::Order(false)) + } else if self.parse_keyword(Keyword::COMMENT) { + self.expect_token(&Token::Eq)?; + Some(SequenceOptions::Comment(self.parse_literal_string()?)) + } else if allow_start && self.parse_keyword(Keyword::MINVALUE) { + Some(SequenceOptions::MinValue(Some(self.parse_number()?))) + } else if allow_start && self.parse_keywords(&[Keyword::NO, Keyword::MINVALUE]) { + Some(SequenceOptions::MinValue(None)) + } else if allow_start && self.parse_keyword(Keyword::MAXVALUE) { + Some(SequenceOptions::MaxValue(Some(self.parse_number()?))) + } else if allow_start && self.parse_keywords(&[Keyword::NO, Keyword::MAXVALUE]) { + Some(SequenceOptions::MaxValue(None)) + } else if allow_start && self.parse_keyword(Keyword::CACHE) { + Some(SequenceOptions::Cache(self.parse_number()?)) + } else if allow_start && self.parse_keywords(&[Keyword::NO, Keyword::CYCLE]) { + Some(SequenceOptions::Cycle(true)) + } else if allow_start && self.parse_keyword(Keyword::CYCLE) { + Some(SequenceOptions::Cycle(false)) + } else { + None + }; + match option { + Some(option) => sequence_options.push(option), + None => break, + } + } + Ok(sequence_options) + } + fn parse_create_sequence_options(&mut self) -> Result, ParserError> { let mut sequence_options = vec![]; //[ INCREMENT [ BY ] increment ] diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index e0cea510e..12e5792a0 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -5667,3 +5667,92 @@ fn test_named_file_format_lifecycle() { ); } } + +/// Snowflake sequence syntax: +/// +/// +/// +#[test] +fn test_sequence_lifecycle() { + let create = snowflake().one_statement_parses_to( + "CREATE OR REPLACE SEQUENCE analytics.raw.event_ids WITH START WITH = 10 INCREMENT BY = 5 ORDER COMMENT = 'owner''s sequence'", + "CREATE OR REPLACE SEQUENCE analytics.raw.event_ids START WITH 10 INCREMENT BY 5 ORDER COMMENT = 'owner''s sequence'", + ); + match create { + Statement::CreateSequence { + or_replace, + or_alter, + temporary, + if_not_exists, + name, + sequence_options, + .. + } => { + assert!(or_replace); + assert!(!or_alter); + assert!(!temporary); + assert!(!if_not_exists); + assert_eq!("analytics.raw.event_ids", name.to_string()); + assert_eq!(4, sequence_options.len()); + assert!(matches!(sequence_options[2], SequenceOptions::Order(true))); + assert!(matches!( + &sequence_options[3], + SequenceOptions::Comment(comment) if comment == "owner's sequence" + )); + } + statement => panic!("unexpected statement: {statement:?}"), + } + + match snowflake().verified_stmt( + "CREATE OR ALTER SEQUENCE analytics.raw.event_ids INCREMENT -3 NOORDER COMMENT = 'updated'", + ) { + Statement::CreateSequence { + or_replace, + or_alter, + sequence_options, + .. + } => { + assert!(!or_replace); + assert!(or_alter); + assert_eq!(3, sequence_options.len()); + } + statement => panic!("unexpected statement: {statement:?}"), + } + + match snowflake().verified_stmt( + "ALTER SEQUENCE IF EXISTS analytics.raw.event_ids SET INCREMENT BY 7 NOORDER COMMENT = 'changed'", + ) { + Statement::AlterSequence { + if_exists, + name, + operation: AlterSequenceOperation::SetOptions(options), + } => { + assert!(if_exists); + assert_eq!("analytics.raw.event_ids", name.to_string()); + assert_eq!(3, options.len()); + } + statement => panic!("unexpected statement: {statement:?}"), + } + + for sql in [ + "ALTER SEQUENCE analytics.raw.event_ids RENAME TO archived_event_ids", + "ALTER SEQUENCE analytics.raw.event_ids UNSET COMMENT", + "DESCRIBE SEQUENCE analytics.raw.event_ids", + "DESC SEQUENCE analytics.raw.event_ids", + "SHOW SEQUENCES LIKE 'EVENT%' IN SCHEMA analytics.raw", + "DROP SEQUENCE IF EXISTS analytics.raw.event_ids RESTRICT", + ] { + snowflake().verified_stmt(sql); + } + + for invalid in [ + "ALTER SEQUENCE analytics.raw.event_ids SET", + "ALTER SEQUENCE analytics.raw.event_ids SET START = 1", + "SHOW TERSE SEQUENCES", + ] { + assert!( + snowflake().parse_sql_statements(invalid).is_err(), + "{invalid} should fail" + ); + } +}