Skip to content

Commit fe10fac

Browse files
thomas-jeepebenesch
authored andcommitted
Add FETCH and OFFSET support
1 parent 202464a commit fe10fac

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, 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: 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
@@ -1388,6 +1388,187 @@ fn parse_invalid_subquery_without_parens() {
13881388
);
13891389
}
13901390

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

0 commit comments

Comments
 (0)