Skip to content

Commit

Permalink
feat: Initial support for DECLARE (cursors) (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
ovr committed Jun 6, 2022
1 parent 66a3082 commit 3f1c642
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/ast/mod.rs
Expand Up @@ -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<bool>,
/// None = Not specified
/// Some(true) = SCROLL
/// Some(false) = NO SCROLL
scroll: Option<bool>,
/// 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<bool>,
query: Box<Query>,
},
/// FETCH - retrieve rows from a query using a cursor
///
/// Note: this is a PostgreSQL-specific statement,
Expand Down Expand Up @@ -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,
Expand Down
51 changes: 51 additions & 0 deletions src/parser.rs
Expand Up @@ -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()?),
Expand Down Expand Up @@ -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<Statement, ParserError> {
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<Statement, ParserError> {
let direction = if self.parse_keyword(Keyword::NEXT) {
Expand Down
17 changes: 17 additions & 0 deletions tests/sqlparser_postgres.rs
Expand Up @@ -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\"");
Expand Down

0 comments on commit 3f1c642

Please sign in to comment.