Skip to content

Commit 721c241

Browse files
authored
Merge pull request #69 from thomas-jeepe/master
Add FETCH and OFFSET support, and LATERAL <derived table>
2 parents 202464a + 2d00ea7 commit 721c241

File tree

5 files changed

+368
-7
lines changed

5 files changed

+368
-7
lines changed

src/dialect/keywords.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ define_keywords!(
155155
EXTRACT,
156156
FALSE,
157157
FETCH,
158+
FIRST,
158159
FILTER,
159160
FIRST_VALUE,
160161
FLOAT,
@@ -229,6 +230,7 @@ define_keywords!(
229230
NATURAL,
230231
NCHAR,
231232
NCLOB,
233+
NEXT,
232234
NEW,
233235
NO,
234236
NONE,
@@ -341,6 +343,7 @@ define_keywords!(
341343
TABLESAMPLE,
342344
TEXT,
343345
THEN,
346+
TIES,
344347
TIME,
345348
TIMESTAMP,
346349
TIMEZONE_HOUR,
@@ -396,7 +399,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
396399
// Reserved as both a table and a column alias:
397400
WITH, SELECT, WHERE, GROUP, ORDER, UNION, EXCEPT, INTERSECT,
398401
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
399-
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT,
402+
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT, OFFSET, FETCH,
400403
];
401404

402405
/// Can't be used as a column alias, so that `SELECT <expr> alias`

src/sqlast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ mod table_key;
2121
mod value;
2222

2323
pub use self::query::{
24-
Cte, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, SQLSelectItem,
25-
SQLSetExpr, SQLSetOperator, TableFactor,
24+
Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect,
25+
SQLSelectItem, SQLSetExpr, SQLSetOperator, TableFactor,
2626
};
2727
pub use self::sqltype::SQLType;
2828
pub use self::table_key::{AlterOperation, Key, TableKey};

src/sqlast/query.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ pub struct SQLQuery {
1010
pub body: SQLSetExpr,
1111
/// ORDER BY
1212
pub order_by: Vec<SQLOrderByExpr>,
13-
/// LIMIT
13+
/// LIMIT { <N> | ALL }
1414
pub limit: Option<ASTNode>,
15+
/// OFFSET <N> { ROW | ROWS }
16+
pub offset: Option<ASTNode>,
17+
/// FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }
18+
pub fetch: Option<Fetch>,
1519
}
1620

1721
impl ToString for SQLQuery {
@@ -27,6 +31,13 @@ impl ToString for SQLQuery {
2731
if let Some(ref limit) = self.limit {
2832
s += &format!(" LIMIT {}", limit.to_string());
2933
}
34+
if let Some(ref offset) = self.offset {
35+
s += &format!(" OFFSET {} ROWS", offset.to_string());
36+
}
37+
if let Some(ref fetch) = self.fetch {
38+
s.push(' ');
39+
s += &fetch.to_string();
40+
}
3041
s
3142
}
3243
}
@@ -198,6 +209,7 @@ pub enum TableFactor {
198209
with_hints: Vec<ASTNode>,
199210
},
200211
Derived {
212+
lateral: bool,
201213
subquery: Box<SQLQuery>,
202214
alias: Option<SQLIdent>,
203215
},
@@ -224,8 +236,16 @@ impl ToString for TableFactor {
224236
}
225237
s
226238
}
227-
TableFactor::Derived { subquery, alias } => {
228-
let mut s = format!("({})", subquery.to_string());
239+
TableFactor::Derived {
240+
lateral,
241+
subquery,
242+
alias,
243+
} => {
244+
let mut s = String::new();
245+
if *lateral {
246+
s += "LATERAL ";
247+
}
248+
s += &format!("({})", subquery.to_string());
229249
if let Some(alias) = alias {
230250
s += &format!(" AS {}", alias);
231251
}
@@ -320,3 +340,27 @@ impl ToString for SQLOrderByExpr {
320340
}
321341
}
322342
}
343+
344+
#[derive(Debug, Clone, PartialEq)]
345+
pub struct Fetch {
346+
pub with_ties: bool,
347+
pub percent: bool,
348+
pub quantity: Option<ASTNode>,
349+
}
350+
351+
impl ToString for Fetch {
352+
fn to_string(&self) -> String {
353+
let extension = if self.with_ties { "WITH TIES" } else { "ONLY" };
354+
if let Some(ref quantity) = self.quantity {
355+
let percent = if self.percent { " PERCENT" } else { "" };
356+
format!(
357+
"FETCH FIRST {}{} ROWS {}",
358+
quantity.to_string(),
359+
percent,
360+
extension
361+
)
362+
} else {
363+
format!("FETCH FIRST ROWS {}", extension)
364+
}
365+
}
366+
}

src/sqlparser.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,40 @@ impl Parser {
684684
true
685685
}
686686

687+
/// Look for one of the given keywords and return the one that matches.
688+
#[must_use]
689+
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
690+
for keyword in keywords {
691+
assert!(keywords::ALL_KEYWORDS.contains(keyword));
692+
}
693+
match self.peek_token() {
694+
Some(Token::SQLWord(ref k)) => keywords
695+
.iter()
696+
.find(|keyword| keyword.eq_ignore_ascii_case(&k.keyword))
697+
.map(|keyword| {
698+
self.next_token();
699+
*keyword
700+
}),
701+
_ => None,
702+
}
703+
}
704+
705+
/// Bail out if the current token is not one of the expected keywords, or consume it if it is
706+
#[must_use]
707+
pub fn expect_one_of_keywords(
708+
&mut self,
709+
keywords: &[&'static str],
710+
) -> Result<&'static str, ParserError> {
711+
if let Some(keyword) = self.parse_one_of_keywords(keywords) {
712+
Ok(keyword)
713+
} else {
714+
self.expected(
715+
&format!("one of {}", keywords.join(" or ")),
716+
self.peek_token(),
717+
)
718+
}
719+
}
720+
687721
/// Bail out if the current token is not an expected keyword, or consume it if it is
688722
pub fn expect_keyword(&mut self, expected: &'static str) -> Result<(), ParserError> {
689723
if self.parse_keyword(expected) {
@@ -1279,11 +1313,25 @@ impl Parser {
12791313
None
12801314
};
12811315

1316+
let offset = if self.parse_keyword("OFFSET") {
1317+
Some(self.parse_offset()?)
1318+
} else {
1319+
None
1320+
};
1321+
1322+
let fetch = if self.parse_keyword("FETCH") {
1323+
Some(self.parse_fetch()?)
1324+
} else {
1325+
None
1326+
};
1327+
12821328
Ok(SQLQuery {
12831329
ctes,
12841330
body,
12851331
limit,
12861332
order_by,
1333+
offset,
1334+
fetch,
12871335
})
12881336
}
12891337

@@ -1416,11 +1464,18 @@ impl Parser {
14161464

14171465
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`
14181466
pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> {
1467+
let lateral = self.parse_keyword("LATERAL");
14191468
if self.consume_token(&Token::LParen) {
14201469
let subquery = Box::new(self.parse_query()?);
14211470
self.expect_token(&Token::RParen)?;
14221471
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
1423-
Ok(TableFactor::Derived { subquery, alias })
1472+
Ok(TableFactor::Derived {
1473+
lateral,
1474+
subquery,
1475+
alias,
1476+
})
1477+
} else if lateral {
1478+
self.expected("subquery after LATERAL", self.peek_token())
14241479
} else {
14251480
let name = self.parse_object_name()?;
14261481
// Postgres, MSSQL: table-valued functions:
@@ -1655,6 +1710,40 @@ impl Parser {
16551710
.map(|n| Some(ASTNode::SQLValue(Value::Long(n))))
16561711
}
16571712
}
1713+
1714+
/// Parse an OFFSET clause
1715+
pub fn parse_offset(&mut self) -> Result<ASTNode, ParserError> {
1716+
let value = self
1717+
.parse_literal_int()
1718+
.map(|n| ASTNode::SQLValue(Value::Long(n)))?;
1719+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1720+
Ok(value)
1721+
}
1722+
1723+
/// Parse a FETCH clause
1724+
pub fn parse_fetch(&mut self) -> Result<Fetch, ParserError> {
1725+
self.expect_one_of_keywords(&["FIRST", "NEXT"])?;
1726+
let (quantity, percent) = if self.parse_one_of_keywords(&["ROW", "ROWS"]).is_some() {
1727+
(None, false)
1728+
} else {
1729+
let quantity = self.parse_sql_value()?;
1730+
let percent = self.parse_keyword("PERCENT");
1731+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1732+
(Some(quantity), percent)
1733+
};
1734+
let with_ties = if self.parse_keyword("ONLY") {
1735+
false
1736+
} else if self.parse_keywords(vec!["WITH", "TIES"]) {
1737+
true
1738+
} else {
1739+
return self.expected("one of ONLY or WITH TIES", self.peek_token());
1740+
};
1741+
Ok(Fetch {
1742+
with_ties,
1743+
percent,
1744+
quantity,
1745+
})
1746+
}
16581747
}
16591748

16601749
impl SQLWord {

0 commit comments

Comments
 (0)