Skip to content

Commit d74df47

Browse files
committed
feat: Snowflake WITH column options
1 parent 9fc9009 commit d74df47

7 files changed

Lines changed: 100 additions & 59 deletions

File tree

src/ast/ddl.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,7 @@ pub struct ViewColumnDef {
14221422
pub name: Ident,
14231423
pub data_type: Option<DataType>,
14241424
pub options: Option<Vec<ColumnOption>>,
1425+
pub options_comma_separated: bool,
14251426
}
14261427

14271428
impl fmt::Display for ViewColumnDef {
@@ -1431,7 +1432,11 @@ impl fmt::Display for ViewColumnDef {
14311432
write!(f, " {}", data_type)?;
14321433
}
14331434
if let Some(options) = self.options.as_ref() {
1434-
write!(f, " {}", display_comma_separated(options.as_slice()))?;
1435+
if self.options_comma_separated {
1436+
write!(f, " {}", display_comma_separated(options.as_slice()))?;
1437+
} else {
1438+
write!(f, " {}", display_separated(options.as_slice(), " "))?;
1439+
}
14351440
}
14361441
Ok(())
14371442
}

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,7 @@ impl Spanned for ViewColumnDef {
982982
name,
983983
data_type: _, // todo, DataType
984984
options,
985+
options_comma_separated: _,
985986
} = self;
986987

987988
union_spans(

src/parser/mod.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use alloc::{
2222
};
2323
use core::{
2424
fmt::{self, Display},
25+
ops::Not,
2526
str::FromStr,
2627
};
2728
use helpers::attached_token::AttachedToken;
@@ -10566,17 +10567,7 @@ impl<'a> Parser<'a> {
1056610567
/// Parses a column definition within a view.
1056710568
fn parse_view_column(&mut self) -> Result<ViewColumnDef, ParserError> {
1056810569
let name = self.parse_identifier()?;
10569-
let options = if (dialect_of!(self is BigQueryDialect | GenericDialect)
10570-
&& self.parse_keyword(Keyword::OPTIONS))
10571-
|| (dialect_of!(self is SnowflakeDialect | GenericDialect)
10572-
&& self.parse_keyword(Keyword::COMMENT))
10573-
{
10574-
self.prev_token();
10575-
self.parse_optional_column_option()?
10576-
.map(|option| vec![option])
10577-
} else {
10578-
None
10579-
};
10570+
let options = self.parse_view_column_options()?;
1058010571
let data_type = if dialect_of!(self is ClickHouseDialect) {
1058110572
Some(self.parse_data_type()?)
1058210573
} else {
@@ -10586,9 +10577,23 @@ impl<'a> Parser<'a> {
1058610577
name,
1058710578
data_type,
1058810579
options,
10580+
options_comma_separated: !dialect_of!(self is SnowflakeDialect),
1058910581
})
1059010582
}
1059110583

10584+
fn parse_view_column_options(&mut self) -> Result<Option<Vec<ColumnOption>>, ParserError> {
10585+
let mut options = Vec::new();
10586+
loop {
10587+
let option = self.parse_optional_column_option()?;
10588+
if let Some(option) = option {
10589+
options.push(option);
10590+
} else {
10591+
break;
10592+
}
10593+
}
10594+
Ok(options.is_empty().not().then_some(options))
10595+
}
10596+
1059210597
/// Parses a parenthesized comma-separated list of unqualified, possibly quoted identifiers.
1059310598
/// For example: `(col1, "col 2", ...)`
1059410599
pub fn parse_parenthesized_column_list(

tests/sqlparser_bigquery.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ fn parse_create_view_with_options() {
348348
name: Ident::new("name"),
349349
data_type: None,
350350
options: None,
351+
options_comma_separated: true,
351352
},
352353
ViewColumnDef {
353354
name: Ident::new("age"),
@@ -360,6 +361,7 @@ fn parse_create_view_with_options() {
360361
)
361362
),
362363
}])]),
364+
options_comma_separated: true,
363365
},
364366
],
365367
columns

tests/sqlparser_clickhouse.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,8 @@ fn parse_create_view_with_fields_data_types() {
910910
}]),
911911
vec![]
912912
)),
913-
options: None
913+
options: None,
914+
options_comma_separated: true,
914915
},
915916
ViewColumnDef {
916917
name: "f".into(),
@@ -922,7 +923,8 @@ fn parse_create_view_with_fields_data_types() {
922923
}]),
923924
vec![]
924925
)),
925-
options: None
926+
options: None,
927+
options_comma_separated: true,
926928
},
927929
]
928930
);

tests/sqlparser_common.rs

Lines changed: 55 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7960,52 +7960,64 @@ fn parse_create_view_with_options() {
79607960
#[test]
79617961
fn parse_create_view_with_columns() {
79627962
let sql = "CREATE VIEW v (has, cols) AS SELECT 1, 2";
7963-
// TODO: why does this fail for ClickHouseDialect? (#1449)
7964-
// match all_dialects().verified_stmt(sql) {
7965-
match all_dialects_except(|d| d.is::<ClickHouseDialect>()).verified_stmt(sql) {
7966-
Statement::CreateView {
7967-
or_alter,
7968-
name,
7969-
columns,
7970-
or_replace,
7971-
options,
7972-
query,
7973-
materialized,
7974-
cluster_by,
7975-
comment,
7976-
with_no_schema_binding: late_binding,
7977-
if_not_exists,
7978-
temporary,
7979-
to,
7980-
params,
7981-
} => {
7982-
assert_eq!(or_alter, false);
7983-
assert_eq!("v", name.to_string());
7984-
assert_eq!(
7963+
fn assert_stmt_as_expected(stmt: Statement, options_comma_separated: bool) {
7964+
match stmt {
7965+
Statement::CreateView {
7966+
or_alter,
7967+
name,
79857968
columns,
7986-
vec![Ident::new("has"), Ident::new("cols"),]
7987-
.into_iter()
7988-
.map(|name| ViewColumnDef {
7989-
name,
7990-
data_type: None,
7991-
options: None
7992-
})
7993-
.collect::<Vec<_>>()
7994-
);
7995-
assert_eq!(options, CreateTableOptions::None);
7996-
assert_eq!("SELECT 1, 2", query.to_string());
7997-
assert!(!materialized);
7998-
assert!(!or_replace);
7999-
assert_eq!(cluster_by, vec![]);
8000-
assert!(comment.is_none());
8001-
assert!(!late_binding);
8002-
assert!(!if_not_exists);
8003-
assert!(!temporary);
8004-
assert!(to.is_none());
8005-
assert!(params.is_none());
7969+
or_replace,
7970+
options,
7971+
query,
7972+
materialized,
7973+
cluster_by,
7974+
comment,
7975+
with_no_schema_binding: late_binding,
7976+
if_not_exists,
7977+
temporary,
7978+
to,
7979+
params,
7980+
} => {
7981+
assert_eq!(or_alter, false);
7982+
assert_eq!("v", name.to_string());
7983+
assert_eq!(
7984+
columns,
7985+
vec![Ident::new("has"), Ident::new("cols"),]
7986+
.into_iter()
7987+
.map(|name| ViewColumnDef {
7988+
name,
7989+
data_type: None,
7990+
options: None,
7991+
options_comma_separated,
7992+
})
7993+
.collect::<Vec<_>>()
7994+
);
7995+
assert_eq!(options, CreateTableOptions::None);
7996+
assert_eq!("SELECT 1, 2", query.to_string());
7997+
assert!(!materialized);
7998+
assert!(!or_replace);
7999+
assert_eq!(cluster_by, vec![]);
8000+
assert!(comment.is_none());
8001+
assert!(!late_binding);
8002+
assert!(!if_not_exists);
8003+
assert!(!temporary);
8004+
assert!(to.is_none());
8005+
assert!(params.is_none());
8006+
}
8007+
_ => unreachable!(),
80068008
}
8007-
_ => unreachable!(),
80088009
}
8010+
// TODO: why does this fail for ClickHouseDialect? (#1449)
8011+
// match all_dialects().verified_stmt(sql) {
8012+
assert_stmt_as_expected(
8013+
all_dialects_where(|d| !d.is::<ClickHouseDialect>() && !d.is::<SnowflakeDialect>())
8014+
.verified_stmt(sql),
8015+
true,
8016+
);
8017+
assert_stmt_as_expected(
8018+
all_dialects_where(|d| d.is::<SnowflakeDialect>()).verified_stmt(sql),
8019+
false,
8020+
);
80098021
}
80108022

80118023
#[test]

tests/sqlparser_snowflake.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3087,7 +3087,7 @@ fn view_comment_option_should_be_after_column_list() {
30873087
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t",
30883088
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t",
30893089
] {
3090-
snowflake_and_generic()
3090+
snowflake()
30913091
.verified_stmt(sql);
30923092
}
30933093
}
@@ -3096,7 +3096,7 @@ fn view_comment_option_should_be_after_column_list() {
30963096
fn parse_view_column_descriptions() {
30973097
let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1";
30983098

3099-
match snowflake_and_generic().verified_stmt(sql) {
3099+
match snowflake().verified_stmt(sql) {
31003100
Statement::CreateView { name, columns, .. } => {
31013101
assert_eq!(name.to_string(), "v");
31023102
assert_eq!(
@@ -3106,11 +3106,13 @@ fn parse_view_column_descriptions() {
31063106
name: Ident::new("a"),
31073107
data_type: None,
31083108
options: Some(vec![ColumnOption::Comment("Comment".to_string())]),
3109+
options_comma_separated: false,
31093110
},
31103111
ViewColumnDef {
31113112
name: Ident::new("b"),
31123113
data_type: None,
31133114
options: None,
3115+
options_comma_separated: false,
31143116
}
31153117
]
31163118
);
@@ -4045,3 +4047,15 @@ fn parse_connect_by_root_operator() {
40454047
"sql parser error: Expected an expression, found: FROM"
40464048
);
40474049
}
4050+
4051+
#[test]
4052+
fn test_snowflake_create_view_with_tag() {
4053+
let create_view_with_tag = r#"CREATE VIEW X (COL WITH TAG (pii='email')) AS SELECT * FROM Y"#;
4054+
snowflake().verified_stmt(create_view_with_tag);
4055+
}
4056+
4057+
#[test]
4058+
fn test_snowflake_create_view_with_tag_and_comment() {
4059+
let create_view_with_tag_and_comment = r#"CREATE VIEW X (COL WITH TAG (pii='email') COMMENT 'foobar') AS SELECT * FROM Y"#;
4060+
snowflake().verified_stmt(create_view_with_tag_and_comment);
4061+
}

0 commit comments

Comments
 (0)