diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 88883cfbb..d87730396 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4536,6 +4536,23 @@ pub enum Statement { comment: Option, }, /// ```sql + /// CREATE [ OR REPLACE ] WAREHOUSE [ IF NOT EXISTS ] + /// [ [ WITH ] = [ ... ] ] + /// ``` + /// Snowflake-specific statement to create a virtual warehouse. + /// + /// See + CreateWarehouse { + /// `OR REPLACE` flag. + or_replace: bool, + /// `IF NOT EXISTS` flag. + if_not_exists: bool, + /// Warehouse name. + name: ObjectName, + /// Warehouse properties and parameters (e.g. `WAREHOUSE_SIZE = 'XSMALL'`). + options: KeyValueOptions, + }, + /// ```sql /// ASSERT [AS ] /// ``` Assert { @@ -6261,6 +6278,23 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateWarehouse { + or_replace, + if_not_exists, + name, + options, + } => { + write!( + f, + "CREATE {or_replace}WAREHOUSE {if_not_exists}{name}", + or_replace = if *or_replace { "OR REPLACE " } else { "" }, + if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, + )?; + if !options.options.is_empty() { + write!(f, " {options}")?; + } + Ok(()) + } Statement::CopyIntoSnowflake { kind, into, @@ -8579,6 +8613,8 @@ pub enum ObjectType { User, /// A stream. Stream, + /// A warehouse. + Warehouse, } impl fmt::Display for ObjectType { @@ -8597,6 +8633,7 @@ impl fmt::Display for ObjectType { ObjectType::Type => "TYPE", ObjectType::User => "USER", ObjectType::Stream => "STREAM", + ObjectType::Warehouse => "WAREHOUSE", }) } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 7855ff150..af138e13e 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -461,6 +461,7 @@ impl Spanned for Statement { Statement::CreateMacro { .. } => Span::empty(), Statement::CreateStage { .. } => Span::empty(), Statement::CreateFileFormat { .. } => Span::empty(), + Statement::CreateWarehouse { .. } => Span::empty(), Statement::Assert { .. } => Span::empty(), Statement::Grant { .. } => Span::empty(), Statement::Deny { .. } => Span::empty(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6378d4c4d..5c01b4c2c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5268,9 +5268,11 @@ 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::WAREHOUSE) { + self.parse_create_warehouse(or_replace) } else if or_replace { self.expected_ref( - "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE", + "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION or WAREHOUSE after CREATE OR REPLACE", self.peek_token_ref(), ) } else if self.parse_keyword(Keyword::EXTENSION) { @@ -5415,6 +5417,22 @@ impl<'a> Parser<'a> { }) } + /// Parse a `CREATE WAREHOUSE` statement. + /// + /// See + fn parse_create_warehouse(&mut self, or_replace: bool) -> Result { + let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); + let name = self.parse_object_name(false)?; + let _ = self.parse_keyword(Keyword::WITH); + let options = self.parse_key_value_options(false, &[])?; + Ok(Statement::CreateWarehouse { + or_replace, + if_not_exists, + name, + options, + }) + } + /// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details. pub fn parse_create_secret( &mut self, @@ -7566,6 +7584,8 @@ impl<'a> Parser<'a> { ObjectType::User } else if self.parse_keyword(Keyword::STREAM) { ObjectType::Stream + } else if self.parse_keyword(Keyword::WAREHOUSE) { + ObjectType::Warehouse } else if self.parse_keyword(Keyword::FUNCTION) { return self.parse_drop_function().map(Into::into); } else if self.parse_keyword(Keyword::POLICY) { @@ -7593,7 +7613,7 @@ impl<'a> Parser<'a> { }; } else { return self.expected_ref( - "COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW or USER after DROP", + "COLLATION, CONNECTOR, DATABASE, EXTENSION, FUNCTION, INDEX, OPERATOR, POLICY, PROCEDURE, ROLE, SCHEMA, SECRET, SEQUENCE, STAGE, TABLE, TRIGGER, TYPE, VIEW, MATERIALIZED VIEW, USER or WAREHOUSE after DROP", self.peek_token_ref(), ); }; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e629b71a8..b6b8f8b0e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8839,6 +8839,19 @@ fn parse_drop_user() { verified_stmt("DROP USER IF EXISTS u1"); } +#[test] +fn parse_create_warehouse() { + verified_stmt("CREATE WAREHOUSE my_wh"); + verified_stmt("CREATE OR REPLACE WAREHOUSE IF NOT EXISTS my_wh"); + verified_stmt("CREATE WAREHOUSE my_wh WAREHOUSE_SIZE='XSMALL' AUTO_SUSPEND=60"); + one_statement_parses_to( + "CREATE WAREHOUSE my_wh WITH WAREHOUSE_SIZE = 'XSMALL' AUTO_SUSPEND = 60", + "CREATE WAREHOUSE my_wh WAREHOUSE_SIZE='XSMALL' AUTO_SUSPEND=60", + ); + verified_stmt("DROP WAREHOUSE my_wh"); + verified_stmt("DROP WAREHOUSE IF EXISTS my_wh"); +} + #[test] fn parse_invalid_subquery_without_parens() { let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz");