Skip to content

Commit 8168dff

Browse files
thomas-jeepebenesch
authored andcommitted
Add FETCH and OFFSET support
1 parent d80f9f3 commit 8168dff

File tree

5 files changed

+305
-4
lines changed

5 files changed

+305
-4
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,
402+
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, 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: 36 additions & 1 deletion
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
}
@@ -320,3 +331,27 @@ impl ToString for SQLOrderByExpr {
320331
}
321332
}
322333
}
334+
335+
#[derive(Debug, Clone, PartialEq)]
336+
pub struct Fetch {
337+
pub with_ties: bool,
338+
pub percent: bool,
339+
pub quantity: Option<ASTNode>,
340+
}
341+
342+
impl ToString for Fetch {
343+
fn to_string(&self) -> String {
344+
let extension = if self.with_ties { "WITH TIES" } else { "ONLY" };
345+
if let Some(ref quantity) = self.quantity {
346+
let percent = if self.percent { " PERCENT" } else { "" };
347+
format!(
348+
"FETCH FIRST {}{} ROWS {}",
349+
quantity.to_string(),
350+
percent,
351+
extension
352+
)
353+
} else {
354+
format!("FETCH FIRST ROWS {}", extension)
355+
}
356+
}
357+
}

src/sqlparser.rs

Lines changed: 82 additions & 0 deletions
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

@@ -1655,6 +1703,40 @@ impl Parser {
16551703
.map(|n| Some(ASTNode::SQLValue(Value::Long(n))))
16561704
}
16571705
}
1706+
1707+
/// Parse an OFFSET clause
1708+
pub fn parse_offset(&mut self) -> Result<ASTNode, ParserError> {
1709+
let value = self
1710+
.parse_literal_int()
1711+
.map(|n| ASTNode::SQLValue(Value::Long(n)))?;
1712+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1713+
Ok(value)
1714+
}
1715+
1716+
/// Parse a FETCH clause
1717+
pub fn parse_fetch(&mut self) -> Result<Fetch, ParserError> {
1718+
self.expect_one_of_keywords(&["FIRST", "NEXT"])?;
1719+
let (quantity, percent) = if self.parse_one_of_keywords(&["ROW", "ROWS"]).is_some() {
1720+
(None, false)
1721+
} else {
1722+
let quantity = self.parse_sql_value()?;
1723+
let percent = self.parse_keyword("PERCENT");
1724+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1725+
(Some(quantity), percent)
1726+
};
1727+
let with_ties = if self.parse_keyword("ONLY") {
1728+
false
1729+
} else if self.parse_keywords(vec!["WITH", "TIES"]) {
1730+
true
1731+
} else {
1732+
return self.expected("one of ONLY or WITH TIES", self.peek_token());
1733+
};
1734+
Ok(Fetch {
1735+
with_ties,
1736+
percent,
1737+
quantity,
1738+
})
1739+
}
16581740
}
16591741

16601742
impl SQLWord {

tests/sqlparser_common.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,187 @@ fn parse_invalid_subquery_without_parens() {
13781378
);
13791379
}
13801380

1381+
#[test]
1382+
fn parse_offset() {
1383+
let ast = verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
1384+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1385+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
1386+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1387+
let ast = verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
1388+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1389+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
1390+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1391+
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
1392+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1393+
match ast.body {
1394+
SQLSetExpr::Select(s) => match s.relation {
1395+
Some(TableFactor::Derived { subquery, .. }) => {
1396+
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1397+
}
1398+
_ => panic!("Test broke"),
1399+
},
1400+
_ => panic!("Test broke"),
1401+
}
1402+
}
1403+
1404+
#[test]
1405+
fn parse_singular_row_offset() {
1406+
one_statement_parses_to(
1407+
"SELECT foo FROM bar OFFSET 1 ROW",
1408+
"SELECT foo FROM bar OFFSET 1 ROWS",
1409+
);
1410+
}
1411+
1412+
#[test]
1413+
fn parse_fetch() {
1414+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY");
1415+
assert_eq!(
1416+
ast.fetch,
1417+
Some(Fetch {
1418+
with_ties: false,
1419+
percent: false,
1420+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1421+
})
1422+
);
1423+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY");
1424+
assert_eq!(
1425+
ast.fetch,
1426+
Some(Fetch {
1427+
with_ties: false,
1428+
percent: false,
1429+
quantity: None,
1430+
})
1431+
);
1432+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY");
1433+
assert_eq!(
1434+
ast.fetch,
1435+
Some(Fetch {
1436+
with_ties: false,
1437+
percent: false,
1438+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1439+
})
1440+
);
1441+
let ast = verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY");
1442+
assert_eq!(
1443+
ast.fetch,
1444+
Some(Fetch {
1445+
with_ties: false,
1446+
percent: false,
1447+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1448+
})
1449+
);
1450+
let ast = verified_query(
1451+
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES",
1452+
);
1453+
assert_eq!(
1454+
ast.fetch,
1455+
Some(Fetch {
1456+
with_ties: true,
1457+
percent: false,
1458+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1459+
})
1460+
);
1461+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY");
1462+
assert_eq!(
1463+
ast.fetch,
1464+
Some(Fetch {
1465+
with_ties: false,
1466+
percent: true,
1467+
quantity: Some(ASTNode::SQLValue(Value::Long(50))),
1468+
})
1469+
);
1470+
let ast = verified_query(
1471+
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY",
1472+
);
1473+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1474+
assert_eq!(
1475+
ast.fetch,
1476+
Some(Fetch {
1477+
with_ties: false,
1478+
percent: false,
1479+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1480+
})
1481+
);
1482+
let ast = verified_query(
1483+
"SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY",
1484+
);
1485+
assert_eq!(
1486+
ast.fetch,
1487+
Some(Fetch {
1488+
with_ties: false,
1489+
percent: false,
1490+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1491+
})
1492+
);
1493+
match ast.body {
1494+
SQLSetExpr::Select(s) => match s.relation {
1495+
Some(TableFactor::Derived { subquery, .. }) => {
1496+
assert_eq!(
1497+
subquery.fetch,
1498+
Some(Fetch {
1499+
with_ties: false,
1500+
percent: false,
1501+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1502+
})
1503+
);
1504+
}
1505+
_ => panic!("Test broke"),
1506+
},
1507+
_ => panic!("Test broke"),
1508+
}
1509+
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY");
1510+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1511+
assert_eq!(
1512+
ast.fetch,
1513+
Some(Fetch {
1514+
with_ties: false,
1515+
percent: false,
1516+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1517+
})
1518+
);
1519+
match ast.body {
1520+
SQLSetExpr::Select(s) => match s.relation {
1521+
Some(TableFactor::Derived { subquery, .. }) => {
1522+
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1523+
assert_eq!(
1524+
subquery.fetch,
1525+
Some(Fetch {
1526+
with_ties: false,
1527+
percent: false,
1528+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1529+
})
1530+
);
1531+
}
1532+
_ => panic!("Test broke"),
1533+
},
1534+
_ => panic!("Test broke"),
1535+
}
1536+
}
1537+
1538+
#[test]
1539+
fn parse_fetch_variations() {
1540+
one_statement_parses_to(
1541+
"SELECT foo FROM bar FETCH FIRST 10 ROW ONLY",
1542+
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
1543+
);
1544+
one_statement_parses_to(
1545+
"SELECT foo FROM bar FETCH NEXT 10 ROW ONLY",
1546+
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
1547+
);
1548+
one_statement_parses_to(
1549+
"SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES",
1550+
"SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES",
1551+
);
1552+
one_statement_parses_to(
1553+
"SELECT foo FROM bar FETCH NEXT ROWS WITH TIES",
1554+
"SELECT foo FROM bar FETCH FIRST ROWS WITH TIES",
1555+
);
1556+
one_statement_parses_to(
1557+
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
1558+
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
1559+
);
1560+
}
1561+
13811562
#[test]
13821563
#[should_panic(
13831564
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"

0 commit comments

Comments
 (0)