Skip to content

Commit 66a3082

Browse files
authored
feat: Support FETCH (cursors) (#510)
1 parent d19d955 commit 66a3082

File tree

4 files changed

+180
-0
lines changed

4 files changed

+180
-0
lines changed

src/ast/mod.rs

+89
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,17 @@ pub enum Statement {
895895
/// deleted along with the dropped table
896896
purge: bool,
897897
},
898+
/// FETCH - retrieve rows from a query using a cursor
899+
///
900+
/// Note: this is a PostgreSQL-specific statement,
901+
/// but may also compatible with other SQL.
902+
Fetch {
903+
/// Cursor name
904+
name: Ident,
905+
direction: FetchDirection,
906+
/// Optional, It's possible to fetch rows form cursor to the table
907+
into: Option<ObjectName>,
908+
},
898909
/// DISCARD [ ALL | PLANS | SEQUENCES | TEMPORARY | TEMP ]
899910
///
900911
/// Note: this is a PostgreSQL-specific statement,
@@ -1114,6 +1125,21 @@ impl fmt::Display for Statement {
11141125
write!(f, "{}", statement)
11151126
}
11161127
Statement::Query(s) => write!(f, "{}", s),
1128+
Statement::Fetch {
1129+
name,
1130+
direction,
1131+
into,
1132+
} => {
1133+
write!(f, "FETCH {} ", direction)?;
1134+
1135+
write!(f, "IN {}", name)?;
1136+
1137+
if let Some(into) = into {
1138+
write!(f, " INTO {}", into)?;
1139+
}
1140+
1141+
Ok(())
1142+
}
11171143
Statement::Directory {
11181144
overwrite,
11191145
local,
@@ -1859,6 +1885,69 @@ impl fmt::Display for Privileges {
18591885
}
18601886
}
18611887

1888+
/// Specific direction for FETCH statement
1889+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1890+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1891+
pub enum FetchDirection {
1892+
Count { limit: Value },
1893+
Next,
1894+
Prior,
1895+
First,
1896+
Last,
1897+
Absolute { limit: Value },
1898+
Relative { limit: Value },
1899+
All,
1900+
// FORWARD
1901+
// FORWARD count
1902+
Forward { limit: Option<Value> },
1903+
ForwardAll,
1904+
// BACKWARD
1905+
// BACKWARD count
1906+
Backward { limit: Option<Value> },
1907+
BackwardAll,
1908+
}
1909+
1910+
impl fmt::Display for FetchDirection {
1911+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1912+
match self {
1913+
FetchDirection::Count { limit } => f.write_str(&limit.to_string())?,
1914+
FetchDirection::Next => f.write_str("NEXT")?,
1915+
FetchDirection::Prior => f.write_str("PRIOR")?,
1916+
FetchDirection::First => f.write_str("FIRST")?,
1917+
FetchDirection::Last => f.write_str("LAST")?,
1918+
FetchDirection::Absolute { limit } => {
1919+
f.write_str("ABSOLUTE ")?;
1920+
f.write_str(&limit.to_string())?;
1921+
}
1922+
FetchDirection::Relative { limit } => {
1923+
f.write_str("RELATIVE ")?;
1924+
f.write_str(&limit.to_string())?;
1925+
}
1926+
FetchDirection::All => f.write_str("ALL")?,
1927+
FetchDirection::Forward { limit } => {
1928+
f.write_str("FORWARD")?;
1929+
1930+
if let Some(l) = limit {
1931+
f.write_str(" ")?;
1932+
f.write_str(&l.to_string())?;
1933+
}
1934+
}
1935+
FetchDirection::ForwardAll => f.write_str("FORWARD ALL")?,
1936+
FetchDirection::Backward { limit } => {
1937+
f.write_str("BACKWARD")?;
1938+
1939+
if let Some(l) = limit {
1940+
f.write_str(" ")?;
1941+
f.write_str(&l.to_string())?;
1942+
}
1943+
}
1944+
FetchDirection::BackwardAll => f.write_str("BACKWARD ALL")?,
1945+
};
1946+
1947+
Ok(())
1948+
}
1949+
}
1950+
18621951
/// A privilege on a database object (table, sequence, etc.).
18631952
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18641953
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

src/keywords.rs

+5
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ macro_rules! define_keywords {
6767
define_keywords!(
6868
ABORT,
6969
ABS,
70+
ABSOLUTE,
7071
ACTION,
7172
ADD,
7273
ALL,
@@ -93,6 +94,7 @@ define_keywords!(
9394
AUTO_INCREMENT,
9495
AVG,
9596
AVRO,
97+
BACKWARD,
9698
BEGIN,
9799
BEGIN_FRAME,
98100
BEGIN_PARTITION,
@@ -239,6 +241,7 @@ define_keywords!(
239241
FORCE_QUOTE,
240242
FOREIGN,
241243
FORMAT,
244+
FORWARD,
242245
FRAME_ROW,
243246
FREE,
244247
FREEZE,
@@ -387,6 +390,7 @@ define_keywords!(
387390
PREPARE,
388391
PRESERVE,
389392
PRIMARY,
393+
PRIOR,
390394
PRIVILEGES,
391395
PROCEDURE,
392396
PROGRAM,
@@ -415,6 +419,7 @@ define_keywords!(
415419
REGR_SXX,
416420
REGR_SXY,
417421
REGR_SYY,
422+
RELATIVE,
418423
RELEASE,
419424
RENAME,
420425
REPAIR,

src/parser.rs

+62
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ impl<'a> Parser<'a> {
167167
Keyword::CREATE => Ok(self.parse_create()?),
168168
Keyword::DROP => Ok(self.parse_drop()?),
169169
Keyword::DISCARD => Ok(self.parse_discard()?),
170+
Keyword::FETCH => Ok(self.parse_fetch_statement()?),
170171
Keyword::DELETE => Ok(self.parse_delete()?),
171172
Keyword::INSERT => Ok(self.parse_insert()?),
172173
Keyword::UPDATE => Ok(self.parse_update()?),
@@ -1824,6 +1825,67 @@ impl<'a> Parser<'a> {
18241825
})
18251826
}
18261827

1828+
// FETCH [ direction { FROM | IN } ] cursor INTO target;
1829+
pub fn parse_fetch_statement(&mut self) -> Result<Statement, ParserError> {
1830+
let direction = if self.parse_keyword(Keyword::NEXT) {
1831+
FetchDirection::Next
1832+
} else if self.parse_keyword(Keyword::PRIOR) {
1833+
FetchDirection::Prior
1834+
} else if self.parse_keyword(Keyword::FIRST) {
1835+
FetchDirection::First
1836+
} else if self.parse_keyword(Keyword::LAST) {
1837+
FetchDirection::Last
1838+
} else if self.parse_keyword(Keyword::ABSOLUTE) {
1839+
FetchDirection::Absolute {
1840+
limit: self.parse_number_value()?,
1841+
}
1842+
} else if self.parse_keyword(Keyword::RELATIVE) {
1843+
FetchDirection::Relative {
1844+
limit: self.parse_number_value()?,
1845+
}
1846+
} else if self.parse_keyword(Keyword::FORWARD) {
1847+
if self.parse_keyword(Keyword::ALL) {
1848+
FetchDirection::ForwardAll
1849+
} else {
1850+
FetchDirection::Forward {
1851+
// TODO: Support optional
1852+
limit: Some(self.parse_number_value()?),
1853+
}
1854+
}
1855+
} else if self.parse_keyword(Keyword::BACKWARD) {
1856+
if self.parse_keyword(Keyword::ALL) {
1857+
FetchDirection::BackwardAll
1858+
} else {
1859+
FetchDirection::Backward {
1860+
// TODO: Support optional
1861+
limit: Some(self.parse_number_value()?),
1862+
}
1863+
}
1864+
} else if self.parse_keyword(Keyword::ALL) {
1865+
FetchDirection::All
1866+
} else {
1867+
FetchDirection::Count {
1868+
limit: self.parse_number_value()?,
1869+
}
1870+
};
1871+
1872+
self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?;
1873+
1874+
let name = self.parse_identifier()?;
1875+
1876+
let into = if self.parse_keyword(Keyword::INTO) {
1877+
Some(self.parse_object_name()?)
1878+
} else {
1879+
None
1880+
};
1881+
1882+
Ok(Statement::Fetch {
1883+
name,
1884+
direction,
1885+
into,
1886+
})
1887+
}
1888+
18271889
pub fn parse_discard(&mut self) -> Result<Statement, ParserError> {
18281890
let object_type = if self.parse_keyword(Keyword::ALL) {
18291891
DiscardObject::ALL

tests/sqlparser_postgres.rs

+24
Original file line numberDiff line numberDiff line change
@@ -1513,3 +1513,27 @@ fn parse_escaped_literal_string() {
15131513
"sql parser error: Unterminated encoded string literal at Line: 1, Column 8"
15141514
);
15151515
}
1516+
1517+
#[test]
1518+
fn parse_fetch() {
1519+
pg_and_generic().verified_stmt("FETCH 2048 IN \"SQL_CUR0x7fa44801bc00\"");
1520+
pg_and_generic().verified_stmt("FETCH 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1521+
pg_and_generic().verified_stmt("FETCH NEXT IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1522+
pg_and_generic().verified_stmt("FETCH PRIOR IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1523+
pg_and_generic().verified_stmt("FETCH FIRST IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1524+
pg_and_generic().verified_stmt("FETCH LAST IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1525+
pg_and_generic()
1526+
.verified_stmt("FETCH ABSOLUTE 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1527+
pg_and_generic()
1528+
.verified_stmt("FETCH RELATIVE 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1529+
pg_and_generic().verified_stmt("FETCH ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1530+
pg_and_generic().verified_stmt("FETCH ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1531+
pg_and_generic()
1532+
.verified_stmt("FETCH FORWARD 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1533+
pg_and_generic()
1534+
.verified_stmt("FETCH FORWARD ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1535+
pg_and_generic()
1536+
.verified_stmt("FETCH BACKWARD 2048 IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1537+
pg_and_generic()
1538+
.verified_stmt("FETCH BACKWARD ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
1539+
}

0 commit comments

Comments
 (0)