Skip to content

Commit 2cd2589

Browse files
eliaperantoniayman-sigma
authored andcommitted
Snowflake: support multiple column options in CREATE VIEW (apache#1891)
1 parent bb1dbab commit 2cd2589

File tree

10 files changed

+98
-40
lines changed

10 files changed

+98
-40
lines changed

src/ast/ddl.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,24 @@ impl fmt::Display for ColumnDef {
14261426
pub struct ViewColumnDef {
14271427
pub name: Ident,
14281428
pub data_type: Option<DataType>,
1429-
pub options: Option<Vec<ColumnOption>>,
1429+
pub options: Option<ColumnOptions>,
1430+
}
1431+
1432+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1433+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1434+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1435+
pub enum ColumnOptions {
1436+
CommaSeparated(Vec<ColumnOption>),
1437+
SpaceSeparated(Vec<ColumnOption>),
1438+
}
1439+
1440+
impl ColumnOptions {
1441+
pub fn as_slice(&self) -> &[ColumnOption] {
1442+
match self {
1443+
ColumnOptions::CommaSeparated(options) => options.as_slice(),
1444+
ColumnOptions::SpaceSeparated(options) => options.as_slice(),
1445+
}
1446+
}
14301447
}
14311448

14321449
impl fmt::Display for ViewColumnDef {
@@ -1436,7 +1453,14 @@ impl fmt::Display for ViewColumnDef {
14361453
write!(f, " {}", data_type)?;
14371454
}
14381455
if let Some(options) = self.options.as_ref() {
1439-
write!(f, " {}", display_comma_separated(options.as_slice()))?;
1456+
match options {
1457+
ColumnOptions::CommaSeparated(column_options) => {
1458+
write!(f, " {}", display_comma_separated(column_options.as_slice()))?;
1459+
}
1460+
ColumnOptions::SpaceSeparated(column_options) => {
1461+
write!(f, " {}", display_separated(column_options.as_slice(), " "))?
1462+
}
1463+
}
14401464
}
14411465
Ok(())
14421466
}

src/ast/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ pub use self::ddl::{
6161
AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation,
6262
AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue,
6363
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
64-
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
65-
ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction, Deduplicate,
66-
DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
67-
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
68-
IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition,
69-
ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint, TagsColumnOption,
70-
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
64+
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
65+
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction,
66+
Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode,
67+
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
68+
IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner,
69+
Partition, ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint,
70+
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
71+
ViewColumnDef,
7172
};
7273
pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert};
7374
pub use self::operator::{BinaryOperator, UnaryOperator};

src/ast/spans.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::ast::query::SelectItemQualifiedWildcardKind;
18+
use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions};
1919
use core::iter;
2020

2121
use crate::tokenizer::Span;
@@ -991,10 +991,13 @@ impl Spanned for ViewColumnDef {
991991
options,
992992
} = self;
993993

994-
union_spans(
995-
core::iter::once(name.span)
996-
.chain(options.iter().flat_map(|i| i.iter().map(|k| k.span()))),
997-
)
994+
name.span.union_opt(&options.as_ref().map(|o| o.span()))
995+
}
996+
}
997+
998+
impl Spanned for ColumnOptions {
999+
fn span(&self) -> Span {
1000+
union_spans(self.as_slice().iter().map(|i| i.span()))
9981001
}
9991002
}
10001003

@@ -1055,7 +1058,9 @@ impl Spanned for CreateTableOptions {
10551058
match self {
10561059
CreateTableOptions::None => Span::empty(),
10571060
CreateTableOptions::With(vec) => union_spans(vec.iter().map(|i| i.span())),
1058-
CreateTableOptions::Options(vec) => union_spans(vec.iter().map(|i| i.span())),
1061+
CreateTableOptions::Options(vec) => {
1062+
union_spans(vec.as_slice().iter().map(|i| i.span()))
1063+
}
10591064
CreateTableOptions::Plain(vec) => union_spans(vec.iter().map(|i| i.span())),
10601065
CreateTableOptions::TableProperties(vec) => union_spans(vec.iter().map(|i| i.span())),
10611066
}

src/dialect/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,10 @@ pub trait Dialect: Debug + Any {
10331033
fn supports_set_names(&self) -> bool {
10341034
false
10351035
}
1036+
1037+
fn supports_space_separated_column_options(&self) -> bool {
1038+
false
1039+
}
10361040
}
10371041

10381042
/// This represents the operators for which precedence must be defined

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,10 @@ impl Dialect for SnowflakeDialect {
356356
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
357357
&RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
358358
}
359+
360+
fn supports_space_separated_column_options(&self) -> bool {
361+
true
362+
}
359363
}
360364

361365
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {

src/parser/mod.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10610,17 +10610,7 @@ impl<'a> Parser<'a> {
1061010610
/// Parses a column definition within a view.
1061110611
fn parse_view_column(&mut self) -> Result<ViewColumnDef, ParserError> {
1061210612
let name = self.parse_identifier()?;
10613-
let options = if (dialect_of!(self is BigQueryDialect | GenericDialect)
10614-
&& self.parse_keyword(Keyword::OPTIONS))
10615-
|| (dialect_of!(self is SnowflakeDialect | GenericDialect)
10616-
&& self.parse_keyword(Keyword::COMMENT))
10617-
{
10618-
self.prev_token();
10619-
self.parse_optional_column_option()?
10620-
.map(|option| vec![option])
10621-
} else {
10622-
None
10623-
};
10613+
let options = self.parse_view_column_options()?;
1062410614
let data_type = if dialect_of!(self is ClickHouseDialect) {
1062510615
Some(self.parse_data_type()?)
1062610616
} else {
@@ -10633,6 +10623,25 @@ impl<'a> Parser<'a> {
1063310623
})
1063410624
}
1063510625

10626+
fn parse_view_column_options(&mut self) -> Result<Option<ColumnOptions>, ParserError> {
10627+
let mut options = Vec::new();
10628+
loop {
10629+
let option = self.parse_optional_column_option()?;
10630+
if let Some(option) = option {
10631+
options.push(option);
10632+
} else {
10633+
break;
10634+
}
10635+
}
10636+
if options.is_empty() {
10637+
Ok(None)
10638+
} else if self.dialect.supports_space_separated_column_options() {
10639+
Ok(Some(ColumnOptions::SpaceSeparated(options)))
10640+
} else {
10641+
Ok(Some(ColumnOptions::CommaSeparated(options)))
10642+
}
10643+
}
10644+
1063610645
/// Parses a parenthesized comma-separated list of unqualified, possibly quoted identifiers.
1063710646
/// For example: `(col1, "col 2", ...)`
1063810647
pub fn parse_parenthesized_column_list(

tests/sqlparser_bigquery.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,16 @@ fn parse_create_view_with_options() {
355355
ViewColumnDef {
356356
name: Ident::new("age"),
357357
data_type: None,
358-
options: Some(vec![ColumnOption::Options(vec![SqlOption::KeyValue {
359-
key: Ident::new("description"),
360-
value: Expr::Value(
361-
Value::DoubleQuotedString("field age".to_string()).with_span(
362-
Span::new(Location::new(1, 42), Location::new(1, 52))
363-
)
364-
),
365-
}])]),
358+
options: Some(ColumnOptions::CommaSeparated(vec![ColumnOption::Options(
359+
vec![SqlOption::KeyValue {
360+
key: Ident::new("description"),
361+
value: Expr::Value(
362+
Value::DoubleQuotedString("field age".to_string()).with_span(
363+
Span::new(Location::new(1, 42), Location::new(1, 52))
364+
)
365+
),
366+
}]
367+
)])),
366368
},
367369
],
368370
columns

tests/sqlparser_clickhouse.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ fn parse_create_view_with_fields_data_types() {
914914
}]),
915915
vec![]
916916
)),
917-
options: None
917+
options: None,
918918
},
919919
ViewColumnDef {
920920
name: "f".into(),
@@ -926,7 +926,7 @@ fn parse_create_view_with_fields_data_types() {
926926
}]),
927927
vec![]
928928
)),
929-
options: None
929+
options: None,
930930
},
931931
]
932932
);

tests/sqlparser_common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7991,7 +7991,7 @@ fn parse_create_view_with_columns() {
79917991
.map(|name| ViewColumnDef {
79927992
name,
79937993
data_type: None,
7994-
options: None
7994+
options: None,
79957995
})
79967996
.collect::<Vec<_>>()
79977997
);

tests/sqlparser_snowflake.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,7 +3143,7 @@ fn view_comment_option_should_be_after_column_list() {
31433143
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t",
31443144
"CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t",
31453145
] {
3146-
snowflake_and_generic()
3146+
snowflake()
31473147
.verified_stmt(sql);
31483148
}
31493149
}
@@ -3152,7 +3152,7 @@ fn view_comment_option_should_be_after_column_list() {
31523152
fn parse_view_column_descriptions() {
31533153
let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1";
31543154

3155-
match snowflake_and_generic().verified_stmt(sql) {
3155+
match snowflake().verified_stmt(sql) {
31563156
Statement::CreateView { name, columns, .. } => {
31573157
assert_eq!(name.to_string(), "v");
31583158
assert_eq!(
@@ -3161,7 +3161,9 @@ fn parse_view_column_descriptions() {
31613161
ViewColumnDef {
31623162
name: Ident::new("a"),
31633163
data_type: None,
3164-
options: Some(vec![ColumnOption::Comment("Comment".to_string())]),
3164+
options: Some(ColumnOptions::SpaceSeparated(vec![ColumnOption::Comment(
3165+
"Comment".to_string()
3166+
)])),
31653167
},
31663168
ViewColumnDef {
31673169
name: Ident::new("b"),
@@ -4184,3 +4186,10 @@ fn test_snowflake_fetch_clause_syntax() {
41844186
canonical,
41854187
);
41864188
}
4189+
4190+
#[test]
4191+
fn test_snowflake_create_view_with_multiple_column_options() {
4192+
let create_view_with_tag =
4193+
r#"CREATE VIEW X (COL WITH TAG (pii='email') COMMENT 'foobar') AS SELECT * FROM Y"#;
4194+
snowflake().verified_stmt(create_view_with_tag);
4195+
}

0 commit comments

Comments
 (0)