diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8a37481a4..79a4d7bcd 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4235,6 +4235,15 @@ pub enum Statement { show_options: ShowStatementOptions, }, /// ```sql + /// SHOW STAGES [ LIKE '' ] [ IN { ACCOUNT | DATABASE | SCHEMA } ] + /// ``` + /// Snowflake-specific statement. + /// + ShowStages { + /// Additional options for filtering and scoping the stage listing. + show_options: ShowStatementOptions, + }, + /// ```sql /// SHOW VIEWS /// ``` ShowViews { @@ -4652,6 +4661,18 @@ pub enum Statement { table_name: ObjectName, }, /// ```sql + /// DESC[RIBE] STAGE + /// ``` + /// Snowflake-specific statement. + /// + DescribeStage { + /// `DESC | DESCRIBE` spelling used by the input statement. + describe_alias: DescribeAlias, + /// Stage name. + #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] + stage_name: ObjectName, + }, + /// ```sql /// [EXPLAIN | DESC | DESCRIBE] /// ``` Explain { @@ -5148,6 +5169,10 @@ impl fmt::Display for Statement { write!(f, "{table_name}") } + Statement::DescribeStage { + describe_alias, + stage_name, + } => write!(f, "{describe_alias} STAGE {stage_name}"), Statement::Explain { describe_alias, verbose, @@ -5959,6 +5984,9 @@ impl fmt::Display for Statement { )?; Ok(()) } + Statement::ShowStages { show_options } => { + write!(f, "SHOW STAGES{show_options}") + } Statement::ShowViews { terse, materialized, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index adcda1381..446b87ae0 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -284,6 +284,7 @@ impl Spanned for Values { /// - [Statement::ShowCreate] /// - [Statement::ShowColumns] /// - [Statement::ShowTables] +/// - [Statement::ShowStages] /// - [Statement::ShowCollation] /// - [Statement::StartTransaction] /// - [Statement::Comment] @@ -306,6 +307,7 @@ impl Spanned for Values { /// - [Statement::Prepare] /// - [Statement::Kill] /// - [Statement::ExplainTable] +/// - [Statement::DescribeStage] /// - [Statement::Explain] /// - [Statement::Savepoint] /// - [Statement::ReleaseSavepoint] @@ -443,6 +445,7 @@ impl Spanned for Statement { Statement::ShowCreate { .. } => Span::empty(), Statement::ShowColumns { .. } => Span::empty(), Statement::ShowTables { .. } => Span::empty(), + Statement::ShowStages { .. } => Span::empty(), Statement::ShowCollation { .. } => Span::empty(), Statement::ShowCharset { .. } => Span::empty(), Statement::Use(u) => u.span(), @@ -470,6 +473,7 @@ impl Spanned for Statement { Statement::Prepare { .. } => Span::empty(), Statement::Kill { .. } => Span::empty(), Statement::ExplainTable { .. } => Span::empty(), + Statement::DescribeStage { .. } => Span::empty(), Statement::Explain { .. } => Span::empty(), Statement::Savepoint { .. } => Span::empty(), Statement::ReleaseSavepoint { .. } => Span::empty(), diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 02309cf7c..6aceb191a 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1247,6 +1247,16 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if this dialect supports Snowflake-style `SHOW STAGES`. + fn supports_show_stages(&self) -> bool { + false + } + + /// Returns true if this dialect supports `DESC[RIBE] STAGE `. + fn supports_describe_stage(&self) -> bool { + false + } + /// Returns true if this dialect supports the `COMMENT` statement fn supports_comment_on(&self) -> bool { false diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 03f094a54..eeec285a5 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -479,6 +479,14 @@ impl Dialect for SnowflakeDialect { true } + fn supports_show_stages(&self) -> bool { + true + } + + fn supports_describe_stage(&self) -> bool { + true + } + fn supports_left_associative_joins_without_parens(&self) -> bool { false } diff --git a/src/keywords.rs b/src/keywords.rs index 12d17d084..a45a8ed7e 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -985,6 +985,7 @@ define_keywords!( SRID, STABLE, STAGE, + STAGES, START, STARTS, STATEMENT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0d3b212b6..19459114b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -14020,6 +14020,16 @@ impl<'a> Parser<'a> { } } + if describe_alias != DescribeAlias::Explain + && self.dialect.supports_describe_stage() + && self.parse_keyword(Keyword::STAGE) + { + return Ok(Statement::DescribeStage { + describe_alias, + stage_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()), @@ -15526,6 +15536,10 @@ impl<'a> Parser<'a> { Ok(self.parse_show_columns(extended, full)?) } else if self.parse_keyword(Keyword::TABLES) { Ok(self.parse_show_tables(terse, extended, full, external)?) + } else if self.dialect.supports_show_stages() && self.parse_keyword(Keyword::STAGES) { + Ok(Statement::ShowStages { + 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) { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 539712f3f..d4bf5611e 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -3409,6 +3409,28 @@ fn parse_describe_view() { } } +#[test] +fn parse_describe_stage() { + for sql in [ + "DESCRIBE STAGE analytics.raw.events", + "DESC STAGE analytics.raw.events", + ] { + match snowflake().verified_stmt(sql) { + Statement::DescribeStage { + describe_alias, + stage_name, + } => { + assert!(matches!( + describe_alias, + DescribeAlias::Describe | DescribeAlias::Desc + )); + assert_eq!("analytics.raw.events", stage_name.to_string()); + } + statement => panic!("unexpected statement: {statement:?}"), + } + } +} + #[test] fn parse_use() { let valid_object_names = ["mydb", "CATALOG", "DEFAULT"]; @@ -3701,6 +3723,35 @@ fn test_parse_show_tables() { .verified_stmt("SHOW EXTERNAL TABLES IN SCHEMA STARTS WITH 'abc' LIMIT 20 FROM 'xyz'"); } +#[test] +fn test_parse_show_stages() { + snowflake().verified_stmt("SHOW STAGES"); + snowflake().verified_stmt("SHOW STAGES LIKE 'events%'"); + snowflake().verified_stmt("SHOW STAGES IN ACCOUNT"); + snowflake().verified_stmt("SHOW STAGES IN DATABASE analytics"); + snowflake().verified_stmt("SHOW STAGES IN SCHEMA analytics.raw"); + + match snowflake().verified_stmt("SHOW STAGES LIKE 'events%' IN SCHEMA analytics.raw") { + Statement::ShowStages { show_options } => { + assert!(matches!( + show_options.filter_position, + Some(ShowStatementFilterPosition::Infix( + ShowStatementFilter::Like(ref pattern) + )) if pattern == "events%" + )); + assert!(matches!( + show_options.show_in, + Some(ShowStatementIn { + parent_type: Some(ShowStatementInParentType::Schema), + parent_name: Some(ref name), + .. + }) if name.to_string() == "analytics.raw" + )); + } + statement => panic!("unexpected statement: {statement:?}"), + } +} + #[test] fn test_show_views() { snowflake().verified_stmt("SHOW VIEWS");