From 8004f15e02254f6bc301e829e8e962d95310dbde Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 25 May 2022 19:06:19 +0300 Subject: [PATCH] feat: Support FETCH (cursors) --- src/ast/mod.rs | 87 +++++++++++++++++++++++++++++++++++++ src/keywords.rs | 5 +++ src/parser.rs | 62 ++++++++++++++++++++++++++ tests/sqlparser_postgres.rs | 24 ++++++++++ 4 files changed, 178 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e2ace4cb9..6dad0167d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -926,6 +926,17 @@ pub enum Statement { hold: Option, query: Box, }, + /// FETCH - retrieve rows from a query using a cursor + /// + /// Note: this is a PostgreSQL-specific statement, + /// but may also compatible with other SQL. + Fetch { + /// Cursor name + name: ObjectName, + direction: FetchDirection, + /// Optional, It's possible to fetch rows form cursor to the table + into: Option, + }, /// DISCARD [ ALL | PLANS | SEQUENCES | TEMPORARY | TEMP ] /// /// Note: this is a PostgreSQL-specific statement, @@ -1136,6 +1147,21 @@ impl fmt::Display for Statement { write!(f, "{}", statement) } Statement::Query(s) => write!(f, "{}", s), + Statement::Fetch { + name, + direction, + into, + } => { + write!(f, "FETCH {} ", direction)?; + + write!(f, "IN {}", name)?; + + if let Some(into) = into { + write!(f, " INTO {}", into)?; + } + + Ok(()) + } Statement::Declare { name, binary, @@ -1907,6 +1933,67 @@ impl fmt::Display for Privileges { } } +/// Specific direction for FETCH statement +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum FetchDirection { + Count { limit: Value }, + Next, + Prior, + First, + Last, + Absolute { limit: Value }, + Relative { limit: Value }, + All, + // FORWARD + // FORWARD count + Forward { limit: Option }, + ForwardAll, + // BACKWARD + // BACKWARD count + Backward { limit: Option }, + BackwardAll, +} + +impl fmt::Display for FetchDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FetchDirection::Count { limit } => f.write_str(&limit.to_string())?, + FetchDirection::Next => f.write_str("NEXT")?, + FetchDirection::Prior => f.write_str("PRIOR")?, + FetchDirection::First => f.write_str("FIRST")?, + FetchDirection::Last => f.write_str("LAST")?, + FetchDirection::Absolute { limit } => { + f.write_str("ABSOLUTE ")?; + f.write_str(&limit.to_string())?; + } + FetchDirection::Relative { limit } => { + f.write_str("RELATIVE ")?; + f.write_str(&limit.to_string())?; + } + FetchDirection::All => f.write_str("ALL")?, + FetchDirection::Forward { limit } => { + f.write_str("FORWARD")?; + + if let Some(l) = limit { + f.write_str(&format!(" {}", l))?; + } + } + FetchDirection::ForwardAll => f.write_str("FORWARD ALL")?, + FetchDirection::Backward { limit } => { + f.write_str("BACKWARD")?; + + if let Some(l) = limit { + f.write_str(&format!(" {}", l))?; + } + } + FetchDirection::BackwardAll => f.write_str("BACKWARD ALL")?, + }; + + Ok(()) + } +} + /// A privilege on a database object (table, sequence, etc.). #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/keywords.rs b/src/keywords.rs index b4ddb68dd..99d4a7892 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -67,6 +67,7 @@ macro_rules! define_keywords { define_keywords!( ABORT, ABS, + ABSOLUTE, ACTION, ADD, ALL, @@ -92,6 +93,7 @@ define_keywords!( AUTO_INCREMENT, AVG, AVRO, + BACKWARD, BEGIN, BEGIN_FRAME, BEGIN_PARTITION, @@ -236,6 +238,7 @@ define_keywords!( FORCE_QUOTE, FOREIGN, FORMAT, + FORWARD, FRAME_ROW, FREE, FREEZE, @@ -383,6 +386,7 @@ define_keywords!( PREPARE, PRESERVE, PRIMARY, + PRIOR, PRIVILEGES, PROCEDURE, PROGRAM, @@ -411,6 +415,7 @@ define_keywords!( REGR_SXX, REGR_SXY, REGR_SYY, + RELATIVE, RELEASE, RENAME, REPAIR, diff --git a/src/parser.rs b/src/parser.rs index 31e802caf..2a896533a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -168,6 +168,7 @@ impl<'a> Parser<'a> { 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()?), Keyword::UPDATE => Ok(self.parse_update()?), @@ -1780,6 +1781,67 @@ impl<'a> Parser<'a> { }) } + // FETCH [ direction { FROM | IN } ] cursor INTO target; + pub fn parse_fetch_statement(&mut self) -> Result { + let direction = if self.parse_keyword(Keyword::NEXT) { + FetchDirection::Next + } else if self.parse_keyword(Keyword::PRIOR) { + FetchDirection::Prior + } else if self.parse_keyword(Keyword::FIRST) { + FetchDirection::First + } else if self.parse_keyword(Keyword::LAST) { + FetchDirection::Last + } else if self.parse_keyword(Keyword::ABSOLUTE) { + FetchDirection::Absolute { + limit: self.parse_number_value()?, + } + } else if self.parse_keyword(Keyword::RELATIVE) { + FetchDirection::Relative { + limit: self.parse_number_value()?, + } + } else if self.parse_keyword(Keyword::FORWARD) { + if self.parse_keyword(Keyword::ALL) { + FetchDirection::ForwardAll + } else { + FetchDirection::Forward { + // TODO: Support optional + limit: Some(self.parse_number_value()?), + } + } + } else if self.parse_keyword(Keyword::BACKWARD) { + if self.parse_keyword(Keyword::ALL) { + FetchDirection::BackwardAll + } else { + FetchDirection::Backward { + // TODO: Support optional + limit: Some(self.parse_number_value()?), + } + } + } else if self.parse_keyword(Keyword::ALL) { + FetchDirection::All + } else { + FetchDirection::Count { + limit: self.parse_number_value()?, + } + }; + + self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?; + + let name = self.parse_object_name()?; + + let into = if self.parse_keyword(Keyword::INTO) { + Some(self.parse_object_name()?) + } else { + None + }; + + Ok(Statement::Fetch { + name, + direction, + into, + }) + } + /// DECLARE name [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ] // CURSOR [ { WITH | WITHOUT } HOLD ] FOR query pub fn parse_declare(&mut self) -> Result { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 4cb86b63a..dafdaa316 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1484,3 +1484,27 @@ fn parse_declare() { .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\""); + pg_and_generic().verified_stmt("FETCH 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH NEXT IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH PRIOR IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH FIRST IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH LAST IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH ABSOLUTE 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH RELATIVE 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic().verified_stmt("FETCH ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH FORWARD 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH FORWARD ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH BACKWARD 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); + pg_and_generic() + .verified_stmt("FETCH BACKWARD ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\""); +}