diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 88883cfbb..66dafc923 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4536,6 +4536,14 @@ pub enum Statement { comment: Option, }, /// ```sql + /// CREATE [ OR REPLACE ] WAREHOUSE [ IF NOT EXISTS ] + /// [ [ WITH ] = [ ... ] ] + /// ``` + /// Snowflake-specific statement to create a virtual warehouse. + /// + /// See + CreateWarehouse(CreateWarehouse), + /// ```sql /// ASSERT [AS ] /// ``` Assert { @@ -6261,6 +6269,7 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateWarehouse(s) => write!(f, "{s}"), Statement::CopyIntoSnowflake { kind, into, @@ -8579,6 +8588,8 @@ pub enum ObjectType { User, /// A stream. Stream, + /// A warehouse. + Warehouse, } impl fmt::Display for ObjectType { @@ -8597,6 +8608,7 @@ impl fmt::Display for ObjectType { ObjectType::Type => "TYPE", ObjectType::User => "USER", ObjectType::Stream => "STREAM", + ObjectType::Warehouse => "WAREHOUSE", }) } } @@ -11602,6 +11614,45 @@ impl fmt::Display for CreateUser { } } +/// ```sql +/// CREATE [ OR REPLACE ] WAREHOUSE [ IF NOT EXISTS ] +/// [ [ WITH ] = [ ... ] ] +/// ``` +/// Snowflake-specific statement to create a virtual warehouse. +/// +/// See +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct CreateWarehouse { + /// `OR REPLACE` flag. + pub or_replace: bool, + /// `IF NOT EXISTS` flag. + pub if_not_exists: bool, + /// Warehouse name. + pub name: ObjectName, + /// Warehouse properties and parameters (e.g. `WAREHOUSE_SIZE = 'XSMALL'`). + pub options: KeyValueOptions, +} + +impl fmt::Display for CreateWarehouse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CREATE")?; + if self.or_replace { + write!(f, " OR REPLACE")?; + } + write!(f, " WAREHOUSE")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")?; + } + write!(f, " {}", self.name)?; + if !self.options.options.is_empty() { + write!(f, " {}", self.options)?; + } + Ok(()) + } +} + /// Modifies the properties of a user /// /// [Snowflake Syntax:](https://docs.snowflake.com/en/sql-reference/sql/alter-user) @@ -12473,6 +12524,12 @@ impl From for Statement { } } +impl From for Statement { + fn from(c: CreateWarehouse) -> Self { + Self::CreateWarehouse(c) + } +} + impl From for Statement { fn from(v: VacuumStatement) -> Self { Self::Vacuum(v) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 7855ff150..9119d1f4f 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..723b8be68 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).map(Into::into) } 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(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");