Skip to content

Commit 05821cc

Browse files
authored
Add support for PostgreSQL LISTEN/NOTIFY syntax (#1485)
1 parent a9a9d58 commit 05821cc

File tree

6 files changed

+145
-0
lines changed

6 files changed

+145
-0
lines changed

src/ast/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3295,6 +3295,23 @@ pub enum Statement {
32953295
include_final: bool,
32963296
deduplicate: Option<Deduplicate>,
32973297
},
3298+
/// ```sql
3299+
/// LISTEN
3300+
/// ```
3301+
/// listen for a notification channel
3302+
///
3303+
/// See Postgres <https://www.postgresql.org/docs/current/sql-listen.html>
3304+
LISTEN { channel: Ident },
3305+
/// ```sql
3306+
/// NOTIFY channel [ , payload ]
3307+
/// ```
3308+
/// send a notification event together with an optional “payload” string to channel
3309+
///
3310+
/// See Postgres <https://www.postgresql.org/docs/current/sql-notify.html>
3311+
NOTIFY {
3312+
channel: Ident,
3313+
payload: Option<String>,
3314+
},
32983315
}
32993316

33003317
impl fmt::Display for Statement {
@@ -4839,6 +4856,17 @@ impl fmt::Display for Statement {
48394856
}
48404857
Ok(())
48414858
}
4859+
Statement::LISTEN { channel } => {
4860+
write!(f, "LISTEN {channel}")?;
4861+
Ok(())
4862+
}
4863+
Statement::NOTIFY { channel, payload } => {
4864+
write!(f, "NOTIFY {channel}")?;
4865+
if let Some(payload) = payload {
4866+
write!(f, ", '{payload}'")?;
4867+
}
4868+
Ok(())
4869+
}
48424870
}
48434871
}
48444872
}

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,16 @@ pub trait Dialect: Debug + Any {
590590
fn supports_try_convert(&self) -> bool {
591591
false
592592
}
593+
594+
/// Returns true if the dialect supports the `LISTEN` statement
595+
fn supports_listen(&self) -> bool {
596+
false
597+
}
598+
599+
/// Returns true if the dialect supports the `NOTIFY` statement
600+
fn supports_notify(&self) -> bool {
601+
false
602+
}
593603
}
594604

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

src/dialect/postgresql.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ impl Dialect for PostgreSqlDialect {
191191
fn supports_explain_with_utility_options(&self) -> bool {
192192
true
193193
}
194+
195+
/// see <https://www.postgresql.org/docs/current/sql-listen.html>
196+
fn supports_listen(&self) -> bool {
197+
true
198+
}
199+
200+
/// see <https://www.postgresql.org/docs/current/sql-notify.html>
201+
fn supports_notify(&self) -> bool {
202+
true
203+
}
194204
}
195205

196206
pub fn parse_comment(parser: &mut Parser) -> Result<Statement, ParserError> {

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ define_keywords!(
438438
LIKE_REGEX,
439439
LIMIT,
440440
LINES,
441+
LISTEN,
441442
LN,
442443
LOAD,
443444
LOCAL,
@@ -513,6 +514,7 @@ define_keywords!(
513514
NOSUPERUSER,
514515
NOT,
515516
NOTHING,
517+
NOTIFY,
516518
NOWAIT,
517519
NO_WRITE_TO_BINLOG,
518520
NTH_VALUE,

src/parser/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,10 @@ impl<'a> Parser<'a> {
532532
Keyword::EXECUTE => self.parse_execute(),
533533
Keyword::PREPARE => self.parse_prepare(),
534534
Keyword::MERGE => self.parse_merge(),
535+
// `LISTEN` and `NOTIFY` are Postgres-specific
536+
// syntaxes. They are used for Postgres statement.
537+
Keyword::LISTEN if self.dialect.supports_listen() => self.parse_listen(),
538+
Keyword::NOTIFY if self.dialect.supports_notify() => self.parse_notify(),
535539
// `PRAGMA` is sqlite specific https://www.sqlite.org/pragma.html
536540
Keyword::PRAGMA => self.parse_pragma(),
537541
Keyword::UNLOAD => self.parse_unload(),
@@ -946,6 +950,21 @@ impl<'a> Parser<'a> {
946950
Ok(Statement::ReleaseSavepoint { name })
947951
}
948952

953+
pub fn parse_listen(&mut self) -> Result<Statement, ParserError> {
954+
let channel = self.parse_identifier(false)?;
955+
Ok(Statement::LISTEN { channel })
956+
}
957+
958+
pub fn parse_notify(&mut self) -> Result<Statement, ParserError> {
959+
let channel = self.parse_identifier(false)?;
960+
let payload = if self.consume_token(&Token::Comma) {
961+
Some(self.parse_literal_string()?)
962+
} else {
963+
None
964+
};
965+
Ok(Statement::NOTIFY { channel, payload })
966+
}
967+
949968
/// Parse an expression prefix.
950969
pub fn parse_prefix(&mut self) -> Result<Expr, ParserError> {
951970
// allow the dialect to override prefix parsing

tests/sqlparser_common.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11399,3 +11399,79 @@ fn test_show_dbs_schemas_tables_views() {
1139911399
verified_stmt("SHOW MATERIALIZED VIEWS FROM db1");
1140011400
verified_stmt("SHOW MATERIALIZED VIEWS FROM db1 'abc'");
1140111401
}
11402+
11403+
#[test]
11404+
fn parse_listen_channel() {
11405+
let dialects = all_dialects_where(|d| d.supports_listen());
11406+
11407+
match dialects.verified_stmt("LISTEN test1") {
11408+
Statement::LISTEN { channel } => {
11409+
assert_eq!(Ident::new("test1"), channel);
11410+
}
11411+
_ => unreachable!(),
11412+
};
11413+
11414+
assert_eq!(
11415+
dialects.parse_sql_statements("LISTEN *").unwrap_err(),
11416+
ParserError::ParserError("Expected: identifier, found: *".to_string())
11417+
);
11418+
11419+
let dialects = all_dialects_where(|d| !d.supports_listen());
11420+
11421+
assert_eq!(
11422+
dialects.parse_sql_statements("LISTEN test1").unwrap_err(),
11423+
ParserError::ParserError("Expected: an SQL statement, found: LISTEN".to_string())
11424+
);
11425+
}
11426+
11427+
#[test]
11428+
fn parse_notify_channel() {
11429+
let dialects = all_dialects_where(|d| d.supports_notify());
11430+
11431+
match dialects.verified_stmt("NOTIFY test1") {
11432+
Statement::NOTIFY { channel, payload } => {
11433+
assert_eq!(Ident::new("test1"), channel);
11434+
assert_eq!(payload, None);
11435+
}
11436+
_ => unreachable!(),
11437+
};
11438+
11439+
match dialects.verified_stmt("NOTIFY test1, 'this is a test notification'") {
11440+
Statement::NOTIFY {
11441+
channel,
11442+
payload: Some(payload),
11443+
} => {
11444+
assert_eq!(Ident::new("test1"), channel);
11445+
assert_eq!("this is a test notification", payload);
11446+
}
11447+
_ => unreachable!(),
11448+
};
11449+
11450+
assert_eq!(
11451+
dialects.parse_sql_statements("NOTIFY *").unwrap_err(),
11452+
ParserError::ParserError("Expected: identifier, found: *".to_string())
11453+
);
11454+
assert_eq!(
11455+
dialects
11456+
.parse_sql_statements("NOTIFY test1, *")
11457+
.unwrap_err(),
11458+
ParserError::ParserError("Expected: literal string, found: *".to_string())
11459+
);
11460+
11461+
let sql_statements = [
11462+
"NOTIFY test1",
11463+
"NOTIFY test1, 'this is a test notification'",
11464+
];
11465+
let dialects = all_dialects_where(|d| !d.supports_notify());
11466+
11467+
for &sql in &sql_statements {
11468+
assert_eq!(
11469+
dialects.parse_sql_statements(sql).unwrap_err(),
11470+
ParserError::ParserError("Expected: an SQL statement, found: NOTIFY".to_string())
11471+
);
11472+
assert_eq!(
11473+
dialects.parse_sql_statements(sql).unwrap_err(),
11474+
ParserError::ParserError("Expected: an SQL statement, found: NOTIFY".to_string())
11475+
);
11476+
}
11477+
}

0 commit comments

Comments
 (0)