Add support for view comments for Snowflake (#1287)

Co-authored-by: Joey Hain <joey@sigmacomputing.com>
This commit is contained in:
Simon Sawert 2024-05-30 18:21:39 +02:00 committed by GitHub
parent c2d84f5683
commit 029a999645
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 94 additions and 1 deletions

View file

@ -1959,6 +1959,9 @@ pub enum Statement {
query: Box<Query>, query: Box<Query>,
options: CreateTableOptions, options: CreateTableOptions,
cluster_by: Vec<Ident>, cluster_by: Vec<Ident>,
/// Snowflake: Views can have comments in Snowflake.
/// <https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax>
comment: Option<String>,
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html> /// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
with_no_schema_binding: bool, with_no_schema_binding: bool,
/// if true, has SQLite `IF NOT EXISTS` clause <https://www.sqlite.org/lang_createview.html> /// if true, has SQLite `IF NOT EXISTS` clause <https://www.sqlite.org/lang_createview.html>
@ -3323,6 +3326,7 @@ impl fmt::Display for Statement {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding, with_no_schema_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -3336,6 +3340,13 @@ impl fmt::Display for Statement {
temporary = if *temporary { "TEMPORARY " } else { "" }, temporary = if *temporary { "TEMPORARY " } else { "" },
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } 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(_)) { if matches!(options, CreateTableOptions::With(_)) {
write!(f, " {options}")?; write!(f, " {options}")?;
} }

View file

@ -4034,6 +4034,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)?; self.expect_keyword(Keyword::AS)?;
let query = self.parse_boxed_query()?; let query = self.parse_boxed_query()?;
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here. // Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
@ -4054,6 +4067,7 @@ impl<'a> Parser<'a> {
or_replace, or_replace,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding, with_no_schema_binding,
if_not_exists, if_not_exists,
temporary, temporary,

View file

@ -309,6 +309,7 @@ fn parse_create_view_if_not_exists() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -320,6 +321,7 @@ fn parse_create_view_if_not_exists() {
assert!(!or_replace); assert!(!or_replace);
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(if_not_exists); assert!(if_not_exists);
assert!(!temporary); assert!(!temporary);

View file

@ -6251,6 +6251,7 @@ fn parse_create_view() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6262,6 +6263,7 @@ fn parse_create_view() {
assert!(!or_replace); assert!(!or_replace);
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);
@ -6305,6 +6307,7 @@ fn parse_create_view_with_columns() {
query, query,
materialized, materialized,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6325,6 +6328,7 @@ fn parse_create_view_with_columns() {
assert!(!materialized); assert!(!materialized);
assert!(!or_replace); assert!(!or_replace);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);
@ -6345,6 +6349,7 @@ fn parse_create_view_temporary() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6356,6 +6361,7 @@ fn parse_create_view_temporary() {
assert!(!or_replace); assert!(!or_replace);
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(temporary); assert!(temporary);
@ -6376,6 +6382,7 @@ fn parse_create_or_replace_view() {
query, query,
materialized, materialized,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6387,6 +6394,7 @@ fn parse_create_or_replace_view() {
assert!(!materialized); assert!(!materialized);
assert!(or_replace); assert!(or_replace);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);
@ -6411,6 +6419,7 @@ fn parse_create_or_replace_materialized_view() {
query, query,
materialized, materialized,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6422,6 +6431,7 @@ fn parse_create_or_replace_materialized_view() {
assert!(materialized); assert!(materialized);
assert!(or_replace); assert!(or_replace);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);
@ -6442,6 +6452,7 @@ fn parse_create_materialized_view() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6453,6 +6464,7 @@ fn parse_create_materialized_view() {
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert!(!or_replace); assert!(!or_replace);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);
@ -6473,6 +6485,7 @@ fn parse_create_materialized_view_with_cluster_by() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -6484,6 +6497,7 @@ fn parse_create_materialized_view_with_cluster_by() {
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert!(!or_replace); assert!(!or_replace);
assert_eq!(cluster_by, vec![Ident::new("foo")]); assert_eq!(cluster_by, vec![Ident::new("foo")]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(!if_not_exists); assert!(!if_not_exists);
assert!(!temporary); assert!(!temporary);

View file

@ -18,7 +18,7 @@ use sqlparser::ast::helpers::stmt_data_loading::{
DataLoadingOption, DataLoadingOptionType, StageLoadSelectItem, DataLoadingOption, DataLoadingOptionType, StageLoadSelectItem,
}; };
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect};
use sqlparser::parser::{ParserError, ParserOptions}; use sqlparser::parser::{ParserError, ParserOptions};
use sqlparser::tokenizer::*; use sqlparser::tokenizer::*;
use test_utils::*; use test_utils::*;
@ -91,6 +91,56 @@ fn test_snowflake_single_line_tokenize() {
assert_eq!(expected, tokens); 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<dyn Dialect>],
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] #[test]
fn test_sf_derived_table_in_parenthesis() { fn test_sf_derived_table_in_parenthesis() {
// Nesting a subquery in an extra set of parentheses is non-standard, // Nesting a subquery in an extra set of parentheses is non-standard,

View file

@ -167,6 +167,7 @@ fn parse_create_view_temporary_if_not_exists() {
materialized, materialized,
options, options,
cluster_by, cluster_by,
comment,
with_no_schema_binding: late_binding, with_no_schema_binding: late_binding,
if_not_exists, if_not_exists,
temporary, temporary,
@ -178,6 +179,7 @@ fn parse_create_view_temporary_if_not_exists() {
assert!(!or_replace); assert!(!or_replace);
assert_eq!(options, CreateTableOptions::None); assert_eq!(options, CreateTableOptions::None);
assert_eq!(cluster_by, vec![]); assert_eq!(cluster_by, vec![]);
assert!(comment.is_none());
assert!(!late_binding); assert!(!late_binding);
assert!(if_not_exists); assert!(if_not_exists);
assert!(temporary); assert!(temporary);