Skip to content

Commit 9f4a772

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 9082448 commit 9f4a772

File tree

8 files changed

+164
-102
lines changed

8 files changed

+164
-102
lines changed

src/ast/mod.rs

+8
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

+4
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

+6-1
Original file line numberDiff line numberDiff line change
@@ -611,11 +611,16 @@ 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
618618
}
619+
620+
/// Returns true if this dialect supports the `COMMENT` statement
621+
fn supports_comment_on(&self) -> bool {
622+
false
623+
}
619624
}
620625

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

src/dialect/postgresql.rs

+6-39
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

+5
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

+47
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

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

tests/sqlparser_postgres.rs

-62
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)