Skip to content

Commit e079d08

Browse files
committed
Add support of COMMENT ON syntax for Snowflake
The COMMENT ON syntax has been supported in PostgreSQL. This PR only moves it into the common and adds `supports_comment_on` to enable this feature. Also, this PR extends `SCHEMA`, `DATABASE`, `USER`, `ROLE` as its allowed object type. This closes #1511.
1 parent 334a5bf commit e079d08

File tree

8 files changed

+166
-102
lines changed

8 files changed

+166
-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
@@ -601,11 +601,16 @@ pub trait Dialect: Debug + Any {
601601
false
602602
}
603603

604-
/// Returns true if this dialect expects the the `TOP` option
604+
/// Returns true if this dialect expects the `TOP` option
605605
/// before the `ALL`/`DISTINCT` options in a `SELECT` statement.
606606
fn supports_top_before_distinct(&self) -> bool {
607607
false
608608
}
609+
610+
/// Returns true if this dialect supports the `COMMENT` statement
611+
fn supports_comment_on(&self) -> bool {
612+
false
613+
}
609614
}
610615

611616
/// 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 {
@@ -201,42 +199,11 @@ impl Dialect for PostgreSqlDialect {
201199
fn supports_notify(&self) -> bool {
202200
true
203201
}
204-
}
205-
206-
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {
207-
let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
208-
209-
parser.expect_keyword(Keyword::ON)?;
210-
let token = parser.next_token();
211-
212-
let (object_type, object_name) = match token.token {
213-
Token::Word(w) if w.keyword == Keyword::COLUMN => {
214-
let object_name = parser.parse_object_name(false)?;
215-
(CommentObject::Column, object_name)
216-
}
217-
Token::Word(w) if w.keyword == Keyword::TABLE => {
218-
let object_name = parser.parse_object_name(false)?;
219-
(CommentObject::Table, object_name)
220-
}
221-
Token::Word(w) if w.keyword == Keyword::EXTENSION => {
222-
let object_name = parser.parse_object_name(false)?;
223-
(CommentObject::Extension, object_name)
224-
}
225-
_ => parser.expected("comment object_type", token)?,
226-
};
227202

228-
parser.expect_keyword(Keyword::IS)?;
229-
let comment = if parser.parse_keyword(Keyword::NULL) {
230-
None
231-
} else {
232-
Some(parser.parse_literal_string()?)
233-
};
234-
Ok(Statement::Comment {
235-
object_type,
236-
object_name,
237-
comment,
238-
if_exists,
239-
})
203+
/// see <https://www.postgresql.org/docs/current/sql-comment.html>
204+
fn supports_comment_on(&self) -> bool {
205+
true
206+
}
240207
}
241208

242209
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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,10 @@ 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() => {
556+
self.parse_comment()
557+
}
554558
_ => self.expected("an SQL statement", next_token),
555559
},
556560
Token::LParen => {
@@ -561,6 +565,51 @@ impl<'a> Parser<'a> {
561565
}
562566
}
563567

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

tests/sqlparser_common.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11532,3 +11532,91 @@ fn test_select_top() {
1153211532
dialects.verified_stmt("SELECT TOP 3 DISTINCT * FROM tbl");
1153311533
dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl");
1153411534
}
11535+
11536+
#[test]
11537+
fn parse_comments() {
11538+
match all_dialects_where(|d| d.supports_comment_on())
11539+
.verified_stmt("COMMENT ON COLUMN tab.name IS 'comment'")
11540+
{
11541+
Statement::Comment {
11542+
object_type,
11543+
object_name,
11544+
comment: Some(comment),
11545+
if_exists,
11546+
} => {
11547+
assert_eq!("comment", comment);
11548+
assert_eq!("tab.name", object_name.to_string());
11549+
assert_eq!(CommentObject::Column, object_type);
11550+
assert!(!if_exists);
11551+
}
11552+
_ => unreachable!(),
11553+
}
11554+
11555+
let object_types = [
11556+
("COLUMN", CommentObject::Column),
11557+
("EXTENSION", CommentObject::Extension),
11558+
("TABLE", CommentObject::Table),
11559+
("SCHEMA", CommentObject::Schema),
11560+
("DATABASE", CommentObject::Database),
11561+
("USER", CommentObject::User),
11562+
("ROLE", CommentObject::Role),
11563+
];
11564+
for (keyword, expected_object_type) in object_types.iter() {
11565+
match all_dialects_where(|d| d.supports_comment_on())
11566+
.verified_stmt(format!("COMMENT IF EXISTS ON {keyword} db.t0 IS 'comment'").as_str())
11567+
{
11568+
Statement::Comment {
11569+
object_type,
11570+
object_name,
11571+
comment: Some(comment),
11572+
if_exists,
11573+
} => {
11574+
assert_eq!("comment", comment);
11575+
assert_eq!("db.t0", object_name.to_string());
11576+
assert_eq!(*expected_object_type, object_type);
11577+
assert!(if_exists);
11578+
}
11579+
_ => unreachable!(),
11580+
}
11581+
}
11582+
11583+
match all_dialects_where(|d| d.supports_comment_on())
11584+
.verified_stmt("COMMENT IF EXISTS ON TABLE public.tab IS NULL")
11585+
{
11586+
Statement::Comment {
11587+
object_type,
11588+
object_name,
11589+
comment: None,
11590+
if_exists,
11591+
} => {
11592+
assert_eq!("public.tab", object_name.to_string());
11593+
assert_eq!(CommentObject::Table, object_type);
11594+
assert!(if_exists);
11595+
}
11596+
_ => unreachable!(),
11597+
}
11598+
11599+
// missing IS statement
11600+
assert_eq!(
11601+
all_dialects_where(|d| d.supports_comment_on())
11602+
.parse_sql_statements("COMMENT ON TABLE t0")
11603+
.unwrap_err(),
11604+
ParserError::ParserError("Expected: IS, found: EOF".to_string())
11605+
);
11606+
11607+
// missing comment literal
11608+
assert_eq!(
11609+
all_dialects_where(|d| d.supports_comment_on())
11610+
.parse_sql_statements("COMMENT ON TABLE t0 IS")
11611+
.unwrap_err(),
11612+
ParserError::ParserError("Expected: literal string, found: EOF".to_string())
11613+
);
11614+
11615+
// unknown object type
11616+
assert_eq!(
11617+
all_dialects_where(|d| d.supports_comment_on())
11618+
.parse_sql_statements("COMMENT ON UNKNOWN t0 IS 'comment'")
11619+
.unwrap_err(),
11620+
ParserError::ParserError("Expected: comment object_type, found: UNKNOWN".to_string())
11621+
);
11622+
}

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)