Skip to content

Commit 6d907d3

Browse files
authored
Add support of COMMENT ON syntax for Snowflake (#1516)
1 parent 76322ba commit 6d907d3

File tree

8 files changed

+164
-102
lines changed

8 files changed

+164
-102
lines changed

src/ast/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,10 @@ pub enum CommentObject {
18841884
Column,
18851885
Table,
18861886
Extension,
1887+
Schema,
1888+
Database,
1889+
User,
1890+
Role,
18871891
}
18881892

18891893
impl fmt::Display for CommentObject {
@@ -1892,6 +1896,10 @@ impl fmt::Display for CommentObject {
18921896
CommentObject::Column => f.write_str("COLUMN"),
18931897
CommentObject::Table => f.write_str("TABLE"),
18941898
CommentObject::Extension => f.write_str("EXTENSION"),
1899+
CommentObject::Schema => f.write_str("SCHEMA"),
1900+
CommentObject::Database => f.write_str("DATABASE"),
1901+
CommentObject::User => f.write_str("USER"),
1902+
CommentObject::Role => f.write_str("ROLE"),
18951903
}
18961904
}
18971905
}

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,8 @@ impl Dialect for GenericDialect {
111111
fn supports_try_convert(&self) -> bool {
112112
true
113113
}
114+
115+
fn supports_comment_on(&self) -> bool {
116+
true
117+
}
114118
}

src/dialect/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ pub trait Dialect: Debug + Any {
611611
false
612612
}
613613

614-
/// Returns true if this dialect expects the the `TOP` option
614+
/// Returns true if this dialect expects the `TOP` option
615615
/// before the `ALL`/`DISTINCT` options in a `SELECT` statement.
616616
fn supports_top_before_distinct(&self) -> bool {
617617
false
@@ -628,6 +628,11 @@ pub trait Dialect: Debug + Any {
628628
fn supports_show_like_before_in(&self) -> bool {
629629
false
630630
}
631+
632+
/// Returns true if this dialect supports the `COMMENT` statement
633+
fn supports_comment_on(&self) -> bool {
634+
false
635+
}
631636
}
632637

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

src/dialect/postgresql.rs

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
// limitations under the License.
2929
use log::debug;
3030

31-
use crate::ast::{CommentObject, ObjectName, Statement, UserDefinedTypeRepresentation};
31+
use crate::ast::{ObjectName, Statement, UserDefinedTypeRepresentation};
3232
use crate::dialect::{Dialect, Precedence};
3333
use crate::keywords::Keyword;
3434
use crate::parser::{Parser, ParserError};
@@ -136,9 +136,7 @@ impl Dialect for PostgreSqlDialect {
136136
}
137137

138138
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
139-
if parser.parse_keyword(Keyword::COMMENT) {
140-
Some(parse_comment(parser))
141-
} else if parser.parse_keyword(Keyword::CREATE) {
139+
if parser.parse_keyword(Keyword::CREATE) {
142140
parser.prev_token(); // unconsume the CREATE in case we don't end up parsing anything
143141
parse_create(parser)
144142
} else {
@@ -206,42 +204,11 @@ impl Dialect for PostgreSqlDialect {
206204
fn supports_factorial_operator(&self) -> bool {
207205
true
208206
}
209-
}
210-
211-
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {
212-
let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
213-
214-
parser.expect_keyword(Keyword::ON)?;
215-
let token = parser.next_token();
216-
217-
let (object_type, object_name) = match token.token {
218-
Token::Word(w) if w.keyword == Keyword::COLUMN => {
219-
let object_name = parser.parse_object_name(false)?;
220-
(CommentObject::Column, object_name)
221-
}
222-
Token::Word(w) if w.keyword == Keyword::TABLE => {
223-
let object_name = parser.parse_object_name(false)?;
224-
(CommentObject::Table, object_name)
225-
}
226-
Token::Word(w) if w.keyword == Keyword::EXTENSION => {
227-
let object_name = parser.parse_object_name(false)?;
228-
(CommentObject::Extension, object_name)
229-
}
230-
_ => parser.expected("comment object_type", token)?,
231-
};
232207

233-
parser.expect_keyword(Keyword::IS)?;
234-
let comment = if parser.parse_keyword(Keyword::NULL) {
235-
None
236-
} else {
237-
Some(parser.parse_literal_string()?)
238-
};
239-
Ok(Statement::Comment {
240-
object_type,
241-
object_name,
242-
comment,
243-
if_exists,
244-
})
208+
/// see <https://www.postgresql.org/docs/current/sql-comment.html>
209+
fn supports_comment_on(&self) -> bool {
210+
true
211+
}
245212
}
246213

247214
pub fn parse_create(parser: &mut Parser) -> Option<Result<Statement, ParserError>> {

src/dialect/snowflake.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ impl Dialect for SnowflakeDialect {
9696
true
9797
}
9898

99+
/// See [doc](https://docs.snowflake.com/en/sql-reference/sql/comment)
100+
fn supports_comment_on(&self) -> bool {
101+
true
102+
}
103+
99104
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
100105
if parser.parse_keyword(Keyword::CREATE) {
101106
// possibly CREATE STAGE

src/parser/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,8 @@ impl<'a> Parser<'a> {
551551
Keyword::OPTIMIZE if dialect_of!(self is ClickHouseDialect | GenericDialect) => {
552552
self.parse_optimize_table()
553553
}
554+
// `COMMENT` is snowflake specific https://docs.snowflake.com/en/sql-reference/sql/comment
555+
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(),
554556
_ => self.expected("an SQL statement", next_token),
555557
},
556558
Token::LParen => {
@@ -561,6 +563,51 @@ impl<'a> Parser<'a> {
561563
}
562564
}
563565

566+
pub fn parse_comment(&mut self) -> Result<Statement, ParserError> {
567+
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
568+
569+
self.expect_keyword(Keyword::ON)?;
570+
let token = self.next_token();
571+
572+
let (object_type, object_name) = match token.token {
573+
Token::Word(w) if w.keyword == Keyword::COLUMN => {
574+
(CommentObject::Column, self.parse_object_name(false)?)
575+
}
576+
Token::Word(w) if w.keyword == Keyword::TABLE => {
577+
(CommentObject::Table, self.parse_object_name(false)?)
578+
}
579+
Token::Word(w) if w.keyword == Keyword::EXTENSION => {
580+
(CommentObject::Extension, self.parse_object_name(false)?)
581+
}
582+
Token::Word(w) if w.keyword == Keyword::SCHEMA => {
583+
(CommentObject::Schema, self.parse_object_name(false)?)
584+
}
585+
Token::Word(w) if w.keyword == Keyword::DATABASE => {
586+
(CommentObject::Database, self.parse_object_name(false)?)
587+
}
588+
Token::Word(w) if w.keyword == Keyword::USER => {
589+
(CommentObject::User, self.parse_object_name(false)?)
590+
}
591+
Token::Word(w) if w.keyword == Keyword::ROLE => {
592+
(CommentObject::Role, self.parse_object_name(false)?)
593+
}
594+
_ => self.expected("comment object_type", token)?,
595+
};
596+
597+
self.expect_keyword(Keyword::IS)?;
598+
let comment = if self.parse_keyword(Keyword::NULL) {
599+
None
600+
} else {
601+
Some(self.parse_literal_string()?)
602+
};
603+
Ok(Statement::Comment {
604+
object_type,
605+
object_name,
606+
comment,
607+
if_exists,
608+
})
609+
}
610+
564611
pub fn parse_flush(&mut self) -> Result<Statement, ParserError> {
565612
let mut channel = None;
566613
let mut tables: Vec<ObjectName> = vec![];

tests/sqlparser_common.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11629,3 +11629,91 @@ fn parse_factorial_operator() {
1162911629
);
1163011630
}
1163111631
}
11632+
11633+
#[test]
11634+
fn parse_comments() {
11635+
match all_dialects_where(|d| d.supports_comment_on())
11636+
.verified_stmt("COMMENT ON COLUMN tab.name IS 'comment'")
11637+
{
11638+
Statement::Comment {
11639+
object_type,
11640+
object_name,
11641+
comment: Some(comment),
11642+
if_exists,
11643+
} => {
11644+
assert_eq!("comment", comment);
11645+
assert_eq!("tab.name", object_name.to_string());
11646+
assert_eq!(CommentObject::Column, object_type);
11647+
assert!(!if_exists);
11648+
}
11649+
_ => unreachable!(),
11650+
}
11651+
11652+
let object_types = [
11653+
("COLUMN", CommentObject::Column),
11654+
("EXTENSION", CommentObject::Extension),
11655+
("TABLE", CommentObject::Table),
11656+
("SCHEMA", CommentObject::Schema),
11657+
("DATABASE", CommentObject::Database),
11658+
("USER", CommentObject::User),
11659+
("ROLE", CommentObject::Role),
11660+
];
11661+
for (keyword, expected_object_type) in object_types.iter() {
11662+
match all_dialects_where(|d| d.supports_comment_on())
11663+
.verified_stmt(format!("COMMENT IF EXISTS ON {keyword} db.t0 IS 'comment'").as_str())
11664+
{
11665+
Statement::Comment {
11666+
object_type,
11667+
object_name,
11668+
comment: Some(comment),
11669+
if_exists,
11670+
} => {
11671+
assert_eq!("comment", comment);
11672+
assert_eq!("db.t0", object_name.to_string());
11673+
assert_eq!(*expected_object_type, object_type);
11674+
assert!(if_exists);
11675+
}
11676+
_ => unreachable!(),
11677+
}
11678+
}
11679+
11680+
match all_dialects_where(|d| d.supports_comment_on())
11681+
.verified_stmt("COMMENT IF EXISTS ON TABLE public.tab IS NULL")
11682+
{
11683+
Statement::Comment {
11684+
object_type,
11685+
object_name,
11686+
comment: None,
11687+
if_exists,
11688+
} => {
11689+
assert_eq!("public.tab", object_name.to_string());
11690+
assert_eq!(CommentObject::Table, object_type);
11691+
assert!(if_exists);
11692+
}
11693+
_ => unreachable!(),
11694+
}
11695+
11696+
// missing IS statement
11697+
assert_eq!(
11698+
all_dialects_where(|d| d.supports_comment_on())
11699+
.parse_sql_statements("COMMENT ON TABLE t0")
11700+
.unwrap_err(),
11701+
ParserError::ParserError("Expected: IS, found: EOF".to_string())
11702+
);
11703+
11704+
// missing comment literal
11705+
assert_eq!(
11706+
all_dialects_where(|d| d.supports_comment_on())
11707+
.parse_sql_statements("COMMENT ON TABLE t0 IS")
11708+
.unwrap_err(),
11709+
ParserError::ParserError("Expected: literal string, found: EOF".to_string())
11710+
);
11711+
11712+
// unknown object type
11713+
assert_eq!(
11714+
all_dialects_where(|d| d.supports_comment_on())
11715+
.parse_sql_statements("COMMENT ON UNKNOWN t0 IS 'comment'")
11716+
.unwrap_err(),
11717+
ParserError::ParserError("Expected: comment object_type, found: UNKNOWN".to_string())
11718+
);
11719+
}

tests/sqlparser_postgres.rs

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2891,68 +2891,6 @@ fn test_composite_value() {
28912891
);
28922892
}
28932893

2894-
#[test]
2895-
fn parse_comments() {
2896-
match pg().verified_stmt("COMMENT ON COLUMN tab.name IS 'comment'") {
2897-
Statement::Comment {
2898-
object_type,
2899-
object_name,
2900-
comment: Some(comment),
2901-
if_exists,
2902-
} => {
2903-
assert_eq!("comment", comment);
2904-
assert_eq!("tab.name", object_name.to_string());
2905-
assert_eq!(CommentObject::Column, object_type);
2906-
assert!(!if_exists);
2907-
}
2908-
_ => unreachable!(),
2909-
}
2910-
2911-
match pg().verified_stmt("COMMENT ON EXTENSION plpgsql IS 'comment'") {
2912-
Statement::Comment {
2913-
object_type,
2914-
object_name,
2915-
comment: Some(comment),
2916-
if_exists,
2917-
} => {
2918-
assert_eq!("comment", comment);
2919-
assert_eq!("plpgsql", object_name.to_string());
2920-
assert_eq!(CommentObject::Extension, object_type);
2921-
assert!(!if_exists);
2922-
}
2923-
_ => unreachable!(),
2924-
}
2925-
2926-
match pg().verified_stmt("COMMENT ON TABLE public.tab IS 'comment'") {
2927-
Statement::Comment {
2928-
object_type,
2929-
object_name,
2930-
comment: Some(comment),
2931-
if_exists,
2932-
} => {
2933-
assert_eq!("comment", comment);
2934-
assert_eq!("public.tab", object_name.to_string());
2935-
assert_eq!(CommentObject::Table, object_type);
2936-
assert!(!if_exists);
2937-
}
2938-
_ => unreachable!(),
2939-
}
2940-
2941-
match pg().verified_stmt("COMMENT IF EXISTS ON TABLE public.tab IS NULL") {
2942-
Statement::Comment {
2943-
object_type,
2944-
object_name,
2945-
comment: None,
2946-
if_exists,
2947-
} => {
2948-
assert_eq!("public.tab", object_name.to_string());
2949-
assert_eq!(CommentObject::Table, object_type);
2950-
assert!(if_exists);
2951-
}
2952-
_ => unreachable!(),
2953-
}
2954-
}
2955-
29562894
#[test]
29572895
fn parse_quoted_identifier() {
29582896
pg_and_generic().verified_stmt(r#"SELECT "quoted "" ident""#);

0 commit comments

Comments
 (0)