diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c9de747c7..b44a341bc 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1958,6 +1958,9 @@ pub enum Statement { query: Box, options: CreateTableOptions, cluster_by: Vec, + /// Snowflake: Views can have comments in Snowflake. + /// + comment: Option, /// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause with_no_schema_binding: bool, /// if true, has SQLite `IF NOT EXISTS` clause @@ -3226,6 +3229,7 @@ impl fmt::Display for Statement { materialized, options, cluster_by, + comment, with_no_schema_binding, if_not_exists, temporary, @@ -3239,6 +3243,13 @@ impl fmt::Display for Statement { temporary = if *temporary { "TEMPORARY " } else { "" }, if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" } )?; + if let Some(comment) = comment { + write!( + f, + " COMMENT = '{}'", + value::escape_single_quote_string(comment) + )?; + } if matches!(options, CreateTableOptions::With(_)) { write!(f, " {options}")?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f88aefd10..f9e997e91 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3903,6 +3903,19 @@ impl<'a> Parser<'a> { }; } + let comment = if dialect_of!(self is SnowflakeDialect | GenericDialect) + && self.parse_keyword(Keyword::COMMENT) + { + self.expect_token(&Token::Eq)?; + let next_token = self.next_token(); + match next_token.token { + Token::SingleQuotedString(str) => Some(str), + _ => self.expected("string literal", next_token)?, + } + } else { + None + }; + self.expect_keyword(Keyword::AS)?; let query = self.parse_boxed_query()?; // Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here. @@ -3923,6 +3936,7 @@ impl<'a> Parser<'a> { or_replace, options, cluster_by, + comment, with_no_schema_binding, if_not_exists, temporary, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 179755e0c..d03040c4c 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -309,6 +309,7 @@ fn parse_create_view_if_not_exists() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -320,6 +321,7 @@ fn parse_create_view_if_not_exists() { assert!(!or_replace); assert_eq!(options, CreateTableOptions::None); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(if_not_exists); assert!(!temporary); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f8b7d0265..36322d52e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -6251,6 +6251,7 @@ fn parse_create_view() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6262,6 +6263,7 @@ fn parse_create_view() { assert!(!or_replace); assert_eq!(options, CreateTableOptions::None); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); @@ -6305,6 +6307,7 @@ fn parse_create_view_with_columns() { query, materialized, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6325,6 +6328,7 @@ fn parse_create_view_with_columns() { assert!(!materialized); assert!(!or_replace); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); @@ -6345,6 +6349,7 @@ fn parse_create_view_temporary() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6356,6 +6361,7 @@ fn parse_create_view_temporary() { assert!(!or_replace); assert_eq!(options, CreateTableOptions::None); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(temporary); @@ -6376,6 +6382,7 @@ fn parse_create_or_replace_view() { query, materialized, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6387,6 +6394,7 @@ fn parse_create_or_replace_view() { assert!(!materialized); assert!(or_replace); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); @@ -6411,6 +6419,7 @@ fn parse_create_or_replace_materialized_view() { query, materialized, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6422,6 +6431,7 @@ fn parse_create_or_replace_materialized_view() { assert!(materialized); assert!(or_replace); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); @@ -6442,6 +6452,7 @@ fn parse_create_materialized_view() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6453,6 +6464,7 @@ fn parse_create_materialized_view() { assert_eq!(options, CreateTableOptions::None); assert!(!or_replace); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); @@ -6473,6 +6485,7 @@ fn parse_create_materialized_view_with_cluster_by() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -6484,6 +6497,7 @@ fn parse_create_materialized_view_with_cluster_by() { assert_eq!(options, CreateTableOptions::None); assert!(!or_replace); assert_eq!(cluster_by, vec![Ident::new("foo")]); + assert!(comment.is_none()); assert!(!late_binding); assert!(!if_not_exists); assert!(!temporary); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 30f2cc601..bf3eb9fac 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -18,7 +18,7 @@ use sqlparser::ast::helpers::stmt_data_loading::{ DataLoadingOption, DataLoadingOptionType, StageLoadSelectItem, }; use sqlparser::ast::*; -use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; +use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect}; use sqlparser::parser::{ParserError, ParserOptions}; use sqlparser::tokenizer::*; use test_utils::*; @@ -91,6 +91,56 @@ fn test_snowflake_single_line_tokenize() { assert_eq!(expected, tokens); } +#[test] +fn parse_sf_create_or_replace_view_with_comment_missing_equal() { + assert!(snowflake_and_generic() + .parse_sql_statements("CREATE OR REPLACE VIEW v COMMENT = 'hello, world' AS SELECT 1") + .is_ok()); + + assert!(snowflake_and_generic() + .parse_sql_statements("CREATE OR REPLACE VIEW v COMMENT 'hello, world' AS SELECT 1") + .is_err()); +} + +#[test] +fn parse_sf_create_or_replace_with_comment_for_snowflake() { + let sql = "CREATE OR REPLACE VIEW v COMMENT = 'hello, world' AS SELECT 1"; + let dialect = test_utils::TestedDialects { + dialects: vec![Box::new(SnowflakeDialect {}) as Box], + options: None, + }; + + match dialect.verified_stmt(sql) { + Statement::CreateView { + name, + columns, + or_replace, + options, + query, + materialized, + cluster_by, + comment, + with_no_schema_binding: late_binding, + if_not_exists, + temporary, + } => { + assert_eq!("v", name.to_string()); + assert_eq!(columns, vec![]); + assert_eq!(options, CreateTableOptions::None); + assert_eq!("SELECT 1", query.to_string()); + assert!(!materialized); + assert!(or_replace); + assert_eq!(cluster_by, vec![]); + assert!(comment.is_some()); + assert_eq!(comment.expect("expected comment"), "hello, world"); + assert!(!late_binding); + assert!(!if_not_exists); + assert!(!temporary); + } + _ => unreachable!(), + } +} + #[test] fn test_sf_derived_table_in_parenthesis() { // Nesting a subquery in an extra set of parentheses is non-standard, diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 5742754c0..fe5346f14 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -167,6 +167,7 @@ fn parse_create_view_temporary_if_not_exists() { materialized, options, cluster_by, + comment, with_no_schema_binding: late_binding, if_not_exists, temporary, @@ -178,6 +179,7 @@ fn parse_create_view_temporary_if_not_exists() { assert!(!or_replace); assert_eq!(options, CreateTableOptions::None); assert_eq!(cluster_by, vec![]); + assert!(comment.is_none()); assert!(!late_binding); assert!(if_not_exists); assert!(temporary);