diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index b5444b8da..ca8d3a22a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -992,6 +992,7 @@ impl fmt::Display for ColumnDef { /// ```sql /// name /// age OPTIONS(description = "age column", tag = "prod") +/// amount COMMENT 'The total amount for the order line' /// created_at DateTime64 /// ``` #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -1000,7 +1001,7 @@ impl fmt::Display for ColumnDef { pub struct ViewColumnDef { pub name: Ident, pub data_type: Option, - pub options: Option>, + pub options: Option>, } impl fmt::Display for ViewColumnDef { @@ -1010,11 +1011,7 @@ impl fmt::Display for ViewColumnDef { write!(f, " {}", data_type)?; } if let Some(options) = self.options.as_ref() { - write!( - f, - " OPTIONS({})", - display_comma_separated(options.as_slice()) - )?; + write!(f, " {}", display_comma_separated(options.as_slice()))?; } Ok(()) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6c851906c..0d60ab350 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3831,6 +3831,12 @@ impl fmt::Display for Statement { .map(|to| format!(" TO {to}")) .unwrap_or_default() )?; + if !columns.is_empty() { + write!(f, " ({})", display_comma_separated(columns))?; + } + if matches!(options, CreateTableOptions::With(_)) { + write!(f, " {options}")?; + } if let Some(comment) = comment { write!( f, @@ -3838,12 +3844,6 @@ impl fmt::Display for Statement { value::escape_single_quote_string(comment) )?; } - if matches!(options, CreateTableOptions::With(_)) { - write!(f, " {options}")?; - } - if !columns.is_empty() { - write!(f, " ({})", display_comma_separated(columns))?; - } if !cluster_by.is_empty() { write!(f, " CLUSTER BY ({})", display_comma_separated(cluster_by))?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e2f4dd508..d925832df 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8192,11 +8192,14 @@ impl<'a> Parser<'a> { /// Parses a column definition within a view. fn parse_view_column(&mut self) -> Result { let name = self.parse_identifier(false)?; - let options = if dialect_of!(self is BigQueryDialect | GenericDialect) - && self.parse_keyword(Keyword::OPTIONS) + let options = if (dialect_of!(self is BigQueryDialect | GenericDialect) + && self.parse_keyword(Keyword::OPTIONS)) + || (dialect_of!(self is SnowflakeDialect | GenericDialect) + && self.parse_keyword(Keyword::COMMENT)) { self.prev_token(); - Some(self.parse_options(Keyword::OPTIONS)?) + self.parse_optional_column_option()? + .map(|option| vec![option]) } else { None }; diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index e051baa8b..0dc6a529c 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -267,10 +267,10 @@ fn parse_create_view_with_options() { ViewColumnDef { name: Ident::new("age"), data_type: None, - options: Some(vec![SqlOption::KeyValue { + options: Some(vec![ColumnOption::Options(vec![SqlOption::KeyValue { key: Ident::new("description"), value: Expr::Value(Value::DoubleQuotedString("field age".to_string())), - }]) + }])]), }, ], columns diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index d0876fc50..903a21854 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2400,3 +2400,42 @@ fn parse_use() { ); } } + +#[test] +fn view_comment_option_should_be_after_column_list() { + for sql in [ + "CREATE OR REPLACE VIEW v (a) COMMENT = 'Comment' AS SELECT a FROM t", + "CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t", + "CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t", + ] { + snowflake_and_generic() + .verified_stmt(sql); + } +} + +#[test] +fn parse_view_column_descriptions() { + let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1"; + + match snowflake_and_generic().verified_stmt(sql) { + Statement::CreateView { name, columns, .. } => { + assert_eq!(name.to_string(), "v"); + assert_eq!( + columns, + vec![ + ViewColumnDef { + name: Ident::new("a"), + data_type: None, + options: Some(vec![ColumnOption::Comment("Comment".to_string())]), + }, + ViewColumnDef { + name: Ident::new("b"), + data_type: None, + options: None, + } + ] + ); + } + _ => unreachable!(), + }; +}