Skip to content

Commit 029a999

Browse files
bombsimonjmhain
andauthored
Add support for view comments for Snowflake (#1287)
Co-authored-by: Joey Hain <[email protected]>
1 parent c2d84f5 commit 029a999

File tree

6 files changed

+94
-1
lines changed

6 files changed

+94
-1
lines changed

src/ast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,9 @@ pub enum Statement {
19591959
query: Box<Query>,
19601960
options: CreateTableOptions,
19611961
cluster_by: Vec<Ident>,
1962+
/// Snowflake: Views can have comments in Snowflake.
1963+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax>
1964+
comment: Option<String>,
19621965
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
19631966
with_no_schema_binding: bool,
19641967
/// if true, has SQLite `IF NOT EXISTS` clause <https://www.sqlite.org/lang_createview.html>
@@ -3323,6 +3326,7 @@ impl fmt::Display for Statement {
33233326
materialized,
33243327
options,
33253328
cluster_by,
3329+
comment,
33263330
with_no_schema_binding,
33273331
if_not_exists,
33283332
temporary,
@@ -3336,6 +3340,13 @@ impl fmt::Display for Statement {
33363340
temporary = if *temporary { "TEMPORARY " } else { "" },
33373341
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }
33383342
)?;
3343+
if let Some(comment) = comment {
3344+
write!(
3345+
f,
3346+
" COMMENT = '{}'",
3347+
value::escape_single_quote_string(comment)
3348+
)?;
3349+
}
33393350
if matches!(options, CreateTableOptions::With(_)) {
33403351
write!(f, " {options}")?;
33413352
}

src/parser/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,6 +4034,19 @@ impl<'a> Parser<'a> {
40344034
};
40354035
}
40364036

4037+
let comment = if dialect_of!(self is SnowflakeDialect | GenericDialect)
4038+
&& self.parse_keyword(Keyword::COMMENT)
4039+
{
4040+
self.expect_token(&Token::Eq)?;
4041+
let next_token = self.next_token();
4042+
match next_token.token {
4043+
Token::SingleQuotedString(str) => Some(str),
4044+
_ => self.expected("string literal", next_token)?,
4045+
}
4046+
} else {
4047+
None
4048+
};
4049+
40374050
self.expect_keyword(Keyword::AS)?;
40384051
let query = self.parse_boxed_query()?;
40394052
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
@@ -4054,6 +4067,7 @@ impl<'a> Parser<'a> {
40544067
or_replace,
40554068
options,
40564069
cluster_by,
4070+
comment,
40574071
with_no_schema_binding,
40584072
if_not_exists,
40594073
temporary,

tests/sqlparser_bigquery.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ fn parse_create_view_if_not_exists() {
309309
materialized,
310310
options,
311311
cluster_by,
312+
comment,
312313
with_no_schema_binding: late_binding,
313314
if_not_exists,
314315
temporary,
@@ -320,6 +321,7 @@ fn parse_create_view_if_not_exists() {
320321
assert!(!or_replace);
321322
assert_eq!(options, CreateTableOptions::None);
322323
assert_eq!(cluster_by, vec![]);
324+
assert!(comment.is_none());
323325
assert!(!late_binding);
324326
assert!(if_not_exists);
325327
assert!(!temporary);

tests/sqlparser_common.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6251,6 +6251,7 @@ fn parse_create_view() {
62516251
materialized,
62526252
options,
62536253
cluster_by,
6254+
comment,
62546255
with_no_schema_binding: late_binding,
62556256
if_not_exists,
62566257
temporary,
@@ -6262,6 +6263,7 @@ fn parse_create_view() {
62626263
assert!(!or_replace);
62636264
assert_eq!(options, CreateTableOptions::None);
62646265
assert_eq!(cluster_by, vec![]);
6266+
assert!(comment.is_none());
62656267
assert!(!late_binding);
62666268
assert!(!if_not_exists);
62676269
assert!(!temporary);
@@ -6305,6 +6307,7 @@ fn parse_create_view_with_columns() {
63056307
query,
63066308
materialized,
63076309
cluster_by,
6310+
comment,
63086311
with_no_schema_binding: late_binding,
63096312
if_not_exists,
63106313
temporary,
@@ -6325,6 +6328,7 @@ fn parse_create_view_with_columns() {
63256328
assert!(!materialized);
63266329
assert!(!or_replace);
63276330
assert_eq!(cluster_by, vec![]);
6331+
assert!(comment.is_none());
63286332
assert!(!late_binding);
63296333
assert!(!if_not_exists);
63306334
assert!(!temporary);
@@ -6345,6 +6349,7 @@ fn parse_create_view_temporary() {
63456349
materialized,
63466350
options,
63476351
cluster_by,
6352+
comment,
63486353
with_no_schema_binding: late_binding,
63496354
if_not_exists,
63506355
temporary,
@@ -6356,6 +6361,7 @@ fn parse_create_view_temporary() {
63566361
assert!(!or_replace);
63576362
assert_eq!(options, CreateTableOptions::None);
63586363
assert_eq!(cluster_by, vec![]);
6364+
assert!(comment.is_none());
63596365
assert!(!late_binding);
63606366
assert!(!if_not_exists);
63616367
assert!(temporary);
@@ -6376,6 +6382,7 @@ fn parse_create_or_replace_view() {
63766382
query,
63776383
materialized,
63786384
cluster_by,
6385+
comment,
63796386
with_no_schema_binding: late_binding,
63806387
if_not_exists,
63816388
temporary,
@@ -6387,6 +6394,7 @@ fn parse_create_or_replace_view() {
63876394
assert!(!materialized);
63886395
assert!(or_replace);
63896396
assert_eq!(cluster_by, vec![]);
6397+
assert!(comment.is_none());
63906398
assert!(!late_binding);
63916399
assert!(!if_not_exists);
63926400
assert!(!temporary);
@@ -6411,6 +6419,7 @@ fn parse_create_or_replace_materialized_view() {
64116419
query,
64126420
materialized,
64136421
cluster_by,
6422+
comment,
64146423
with_no_schema_binding: late_binding,
64156424
if_not_exists,
64166425
temporary,
@@ -6422,6 +6431,7 @@ fn parse_create_or_replace_materialized_view() {
64226431
assert!(materialized);
64236432
assert!(or_replace);
64246433
assert_eq!(cluster_by, vec![]);
6434+
assert!(comment.is_none());
64256435
assert!(!late_binding);
64266436
assert!(!if_not_exists);
64276437
assert!(!temporary);
@@ -6442,6 +6452,7 @@ fn parse_create_materialized_view() {
64426452
materialized,
64436453
options,
64446454
cluster_by,
6455+
comment,
64456456
with_no_schema_binding: late_binding,
64466457
if_not_exists,
64476458
temporary,
@@ -6453,6 +6464,7 @@ fn parse_create_materialized_view() {
64536464
assert_eq!(options, CreateTableOptions::None);
64546465
assert!(!or_replace);
64556466
assert_eq!(cluster_by, vec![]);
6467+
assert!(comment.is_none());
64566468
assert!(!late_binding);
64576469
assert!(!if_not_exists);
64586470
assert!(!temporary);
@@ -6473,6 +6485,7 @@ fn parse_create_materialized_view_with_cluster_by() {
64736485
materialized,
64746486
options,
64756487
cluster_by,
6488+
comment,
64766489
with_no_schema_binding: late_binding,
64776490
if_not_exists,
64786491
temporary,
@@ -6484,6 +6497,7 @@ fn parse_create_materialized_view_with_cluster_by() {
64846497
assert_eq!(options, CreateTableOptions::None);
64856498
assert!(!or_replace);
64866499
assert_eq!(cluster_by, vec![Ident::new("foo")]);
6500+
assert!(comment.is_none());
64876501
assert!(!late_binding);
64886502
assert!(!if_not_exists);
64896503
assert!(!temporary);

tests/sqlparser_snowflake.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use sqlparser::ast::helpers::stmt_data_loading::{
1818
DataLoadingOption, DataLoadingOptionType, StageLoadSelectItem,
1919
};
2020
use sqlparser::ast::*;
21-
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
21+
use sqlparser::dialect::{Dialect, GenericDialect, SnowflakeDialect};
2222
use sqlparser::parser::{ParserError, ParserOptions};
2323
use sqlparser::tokenizer::*;
2424
use test_utils::*;
@@ -91,6 +91,56 @@ fn test_snowflake_single_line_tokenize() {
9191
assert_eq!(expected, tokens);
9292
}
9393

94+
#[test]
95+
fn parse_sf_create_or_replace_view_with_comment_missing_equal() {
96+
assert!(snowflake_and_generic()
97+
.parse_sql_statements("CREATE OR REPLACE VIEW v COMMENT = 'hello, world' AS SELECT 1")
98+
.is_ok());
99+
100+
assert!(snowflake_and_generic()
101+
.parse_sql_statements("CREATE OR REPLACE VIEW v COMMENT 'hello, world' AS SELECT 1")
102+
.is_err());
103+
}
104+
105+
#[test]
106+
fn parse_sf_create_or_replace_with_comment_for_snowflake() {
107+
let sql = "CREATE OR REPLACE VIEW v COMMENT = 'hello, world' AS SELECT 1";
108+
let dialect = test_utils::TestedDialects {
109+
dialects: vec![Box::new(SnowflakeDialect {}) as Box<dyn Dialect>],
110+
options: None,
111+
};
112+
113+
match dialect.verified_stmt(sql) {
114+
Statement::CreateView {
115+
name,
116+
columns,
117+
or_replace,
118+
options,
119+
query,
120+
materialized,
121+
cluster_by,
122+
comment,
123+
with_no_schema_binding: late_binding,
124+
if_not_exists,
125+
temporary,
126+
} => {
127+
assert_eq!("v", name.to_string());
128+
assert_eq!(columns, vec![]);
129+
assert_eq!(options, CreateTableOptions::None);
130+
assert_eq!("SELECT 1", query.to_string());
131+
assert!(!materialized);
132+
assert!(or_replace);
133+
assert_eq!(cluster_by, vec![]);
134+
assert!(comment.is_some());
135+
assert_eq!(comment.expect("expected comment"), "hello, world");
136+
assert!(!late_binding);
137+
assert!(!if_not_exists);
138+
assert!(!temporary);
139+
}
140+
_ => unreachable!(),
141+
}
142+
}
143+
94144
#[test]
95145
fn test_sf_derived_table_in_parenthesis() {
96146
// Nesting a subquery in an extra set of parentheses is non-standard,

tests/sqlparser_sqlite.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ fn parse_create_view_temporary_if_not_exists() {
167167
materialized,
168168
options,
169169
cluster_by,
170+
comment,
170171
with_no_schema_binding: late_binding,
171172
if_not_exists,
172173
temporary,
@@ -178,6 +179,7 @@ fn parse_create_view_temporary_if_not_exists() {
178179
assert!(!or_replace);
179180
assert_eq!(options, CreateTableOptions::None);
180181
assert_eq!(cluster_by, vec![]);
182+
assert!(comment.is_none());
181183
assert!(!late_binding);
182184
assert!(if_not_exists);
183185
assert!(temporary);

0 commit comments

Comments
 (0)