diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a2cdf8981..0382d9379 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -895,6 +895,29 @@ pub enum Statement { /// deleted along with the dropped table purge: bool, }, + /// DECLARE - Declaring Cursor Variables + /// + /// Note: this is a PostgreSQL-specific statement, + /// but may also compatible with other SQL. + Declare { + /// Cursor name + name: Ident, + /// Causes the cursor to return data in binary rather than in text format. + binary: bool, + /// None = Not specified + /// Some(true) = INSENSITIVE + /// Some(false) = ASENSITIVE + sensitive: Option, + /// None = Not specified + /// Some(true) = SCROLL + /// Some(false) = NO SCROLL + scroll: Option, + /// None = Not specified + /// Some(true) = WITH HOLD, specifies that the cursor can continue to be used after the transaction that created it successfully commits + /// Some(false) = WITHOUT HOLD, specifies that the cursor cannot be used outside of the transaction that created it + hold: Option, + query: Box, + }, /// FETCH - retrieve rows from a query using a cursor /// /// Note: this is a PostgreSQL-specific statement, @@ -1125,6 +1148,48 @@ impl fmt::Display for Statement { write!(f, "{}", statement) } Statement::Query(s) => write!(f, "{}", s), + Statement::Declare { + name, + binary, + sensitive, + scroll, + hold, + query, + } => { + write!(f, "DECLARE {} ", name)?; + + if *binary { + write!(f, "BINARY ")?; + } + + if let Some(sensitive) = sensitive { + if *sensitive { + write!(f, "INSENSITIVE ")?; + } else { + write!(f, "ASENSITIVE ")?; + } + } + + if let Some(scroll) = scroll { + if *scroll { + write!(f, "SCROLL ")?; + } else { + write!(f, "NO SCROLL ")?; + } + } + + write!(f, "CURSOR ")?; + + if let Some(hold) = hold { + if *hold { + write!(f, "WITH HOLD ")?; + } else { + write!(f, "WITHOUT HOLD ")?; + } + } + + write!(f, "FOR {}", query) + } Statement::Fetch { name, direction, diff --git a/src/parser.rs b/src/parser.rs index 383248b16..59198fa66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -167,6 +167,7 @@ impl<'a> Parser<'a> { Keyword::CREATE => Ok(self.parse_create()?), Keyword::DROP => Ok(self.parse_drop()?), Keyword::DISCARD => Ok(self.parse_discard()?), + Keyword::DECLARE => Ok(self.parse_declare()?), Keyword::FETCH => Ok(self.parse_fetch_statement()?), Keyword::DELETE => Ok(self.parse_delete()?), Keyword::INSERT => Ok(self.parse_insert()?), @@ -1825,6 +1826,56 @@ impl<'a> Parser<'a> { }) } + /// DECLARE name [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ] + // CURSOR [ { WITH | WITHOUT } HOLD ] FOR query + pub fn parse_declare(&mut self) -> Result { + let name = self.parse_identifier()?; + + let binary = self.parse_keyword(Keyword::BINARY); + let sensitive = if self.parse_keyword(Keyword::INSENSITIVE) { + Some(true) + } else if self.parse_keyword(Keyword::ASENSITIVE) { + Some(false) + } else { + None + }; + let scroll = if self.parse_keyword(Keyword::SCROLL) { + Some(true) + } else if self.parse_keywords(&[Keyword::NO, Keyword::SCROLL]) { + Some(false) + } else { + None + }; + + self.expect_keyword(Keyword::CURSOR)?; + + let hold = match self.parse_one_of_keywords(&[Keyword::WITH, Keyword::WITHOUT]) { + Some(keyword) => { + self.expect_keyword(Keyword::HOLD)?; + + match keyword { + Keyword::WITH => Some(true), + Keyword::WITHOUT => Some(false), + _ => unreachable!(), + } + } + None => None, + }; + + self.expect_keyword(Keyword::FOR)?; + + let query = self.parse_query()?; + + Ok(Statement::Declare { + name, + binary, + sensitive, + scroll, + hold, + query: Box::new(query), + }) + } + // FETCH [ direction { FROM | IN } ] cursor INTO target; pub fn parse_fetch_statement(&mut self) -> Result { let direction = if self.parse_keyword(Keyword::NEXT) { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 84d3b2088..c9359444b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1514,6 +1514,23 @@ fn parse_escaped_literal_string() { ); } +#[test] +fn parse_declare() { + pg_and_generic() + .verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" CURSOR WITH HOLD FOR SELECT 1"); + pg_and_generic() + .verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" CURSOR WITHOUT HOLD FOR SELECT 1"); + pg_and_generic().verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" BINARY CURSOR FOR SELECT 1"); + pg_and_generic() + .verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" ASENSITIVE CURSOR FOR SELECT 1"); + pg_and_generic() + .verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" INSENSITIVE CURSOR FOR SELECT 1"); + pg_and_generic().verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" SCROLL CURSOR FOR SELECT 1"); + pg_and_generic() + .verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" NO SCROLL CURSOR FOR SELECT 1"); + pg_and_generic().verified_stmt("DECLARE \"SQL_CUR0x7fa44801bc00\" BINARY INSENSITIVE SCROLL CURSOR WITH HOLD FOR SELECT * FROM table_name LIMIT 2222"); +} + #[test] fn parse_fetch() { pg_and_generic().verified_stmt("FETCH 2048 IN \"SQL_CUR0x7fa44801bc00\"");