Skip to content

Commit 01c7850

Browse files
thomas-jeepebenesch
authored andcommitted
Add FETCH and OFFSET support
1 parent 4f944dd commit 01c7850

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
@@ -154,6 +154,7 @@ define_keywords!(
154154
EXTRACT,
155155
FALSE,
156156
FETCH,
157+
FIRST,
157158
FILTER,
158159
FIRST_VALUE,
159160
FLOAT,
@@ -227,6 +228,7 @@ define_keywords!(
227228
NATURAL,
228229
NCHAR,
229230
NCLOB,
231+
NEXT,
230232
NEW,
231233
NO,
232234
NONE,
@@ -338,6 +340,7 @@ define_keywords!(
338340
TABLESAMPLE,
339341
TEXT,
340342
THEN,
343+
TIES,
341344
TIME,
342345
TIMESTAMP,
343346
TIMEZONE_HOUR,
@@ -393,7 +396,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
393396
// Reserved as both a table and a column alias:
394397
WITH, SELECT, WHERE, GROUP, ORDER, UNION, EXCEPT, INTERSECT,
395398
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
396-
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING,
399+
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, OFFSET, FETCH,
397400
];
398401

399402
/// 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: Option<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
@@ -651,6 +651,40 @@ impl Parser {
651651
true
652652
}
653653

654+
/// Look for one of the given keywords and return the one that matches.
655+
#[must_use]
656+
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
657+
for keyword in keywords {
658+
assert!(keywords::ALL_KEYWORDS.contains(keyword));
659+
}
660+
match self.peek_token() {
661+
Some(Token::SQLWord(ref k)) => keywords
662+
.iter()
663+
.find(|keyword| keyword.eq_ignore_ascii_case(&k.keyword))
664+
.map(|keyword| {
665+
self.next_token();
666+
*keyword
667+
}),
668+
_ => None,
669+
}
670+
}
671+
672+
/// Bail out if the current token is not one of the expected keywords, or consume it if it is
673+
#[must_use]
674+
pub fn expect_one_of_keywords(
675+
&mut self,
676+
keywords: &[&'static str],
677+
) -> Result<&'static str, ParserError> {
678+
if let Some(keyword) = self.parse_one_of_keywords(keywords) {
679+
Ok(keyword)
680+
} else {
681+
self.expected(
682+
&format!("one of {}", keywords.join(" or ")),
683+
self.peek_token(),
684+
)
685+
}
686+
}
687+
654688
/// Bail out if the current token is not an expected keyword, or consume it if it is
655689
pub fn expect_keyword(&mut self, expected: &'static str) -> Result<(), ParserError> {
656690
if self.parse_keyword(expected) {
@@ -1209,11 +1243,25 @@ impl Parser {
12091243
None
12101244
};
12111245

1246+
let offset = if self.parse_keyword("OFFSET") {
1247+
Some(self.parse_offset()?)
1248+
} else {
1249+
None
1250+
};
1251+
1252+
let fetch = if self.parse_keyword("FETCH") {
1253+
Some(self.parse_fetch()?)
1254+
} else {
1255+
None
1256+
};
1257+
12121258
Ok(SQLQuery {
12131259
ctes,
12141260
body,
12151261
limit,
12161262
order_by,
1263+
offset,
1264+
fetch,
12171265
})
12181266
}
12191267

@@ -1581,6 +1629,40 @@ impl Parser {
15811629
.map(|n| Some(ASTNode::SQLValue(Value::Long(n))))
15821630
}
15831631
}
1632+
1633+
/// Parse an OFFSET clause
1634+
pub fn parse_offset(&mut self) -> Result<ASTNode, ParserError> {
1635+
let value = self
1636+
.parse_literal_int()
1637+
.map(|n| ASTNode::SQLValue(Value::Long(n)))?;
1638+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1639+
Ok(value)
1640+
}
1641+
1642+
/// Parse a FETCH clause
1643+
pub fn parse_fetch(&mut self) -> Result<Fetch, ParserError> {
1644+
self.expect_one_of_keywords(&["FIRST", "NEXT"])?;
1645+
let (quantity, percent) = if self.parse_one_of_keywords(&["ROW", "ROWS"]).is_some() {
1646+
(None, false)
1647+
} else {
1648+
let quantity = self.parse_sql_value()?;
1649+
let percent = self.parse_keyword("PERCENT");
1650+
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
1651+
(Some(quantity), percent)
1652+
};
1653+
let with_ties = if self.parse_keyword("ONLY") {
1654+
false
1655+
} else if self.parse_keywords(vec!["WITH", "TIES"]) {
1656+
true
1657+
} else {
1658+
return self.expected("one of ONLY or WITH TIES", self.peek_token());
1659+
};
1660+
Ok(Fetch {
1661+
with_ties,
1662+
percent,
1663+
quantity,
1664+
})
1665+
}
15841666
}
15851667

15861668
impl SQLWord {

tests/sqlparser_common.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,187 @@ fn parse_invalid_subquery_without_parens() {
12091209
);
12101210
}
12111211

1212+
#[test]
1213+
fn parse_offset() {
1214+
let ast = verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
1215+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1216+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
1217+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1218+
let ast = verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
1219+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1220+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
1221+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1222+
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
1223+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1224+
match ast.body {
1225+
SQLSetExpr::Select(s) => match s.relation {
1226+
Some(TableFactor::Derived { subquery, .. }) => {
1227+
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1228+
}
1229+
_ => panic!("Test broke"),
1230+
},
1231+
_ => panic!("Test broke"),
1232+
}
1233+
}
1234+
1235+
#[test]
1236+
fn parse_singular_row_offset() {
1237+
one_statement_parses_to(
1238+
"SELECT foo FROM bar OFFSET 1 ROW",
1239+
"SELECT foo FROM bar OFFSET 1 ROWS",
1240+
);
1241+
}
1242+
1243+
#[test]
1244+
fn parse_fetch() {
1245+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY");
1246+
assert_eq!(
1247+
ast.fetch,
1248+
Some(Fetch {
1249+
with_ties: false,
1250+
percent: false,
1251+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1252+
})
1253+
);
1254+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY");
1255+
assert_eq!(
1256+
ast.fetch,
1257+
Some(Fetch {
1258+
with_ties: false,
1259+
percent: false,
1260+
quantity: None,
1261+
})
1262+
);
1263+
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY");
1264+
assert_eq!(
1265+
ast.fetch,
1266+
Some(Fetch {
1267+
with_ties: false,
1268+
percent: false,
1269+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1270+
})
1271+
);
1272+
let ast = verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY");
1273+
assert_eq!(
1274+
ast.fetch,
1275+
Some(Fetch {
1276+
with_ties: false,
1277+
percent: false,
1278+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1279+
})
1280+
);
1281+
let ast = verified_query(
1282+
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES",
1283+
);
1284+
assert_eq!(
1285+
ast.fetch,
1286+
Some(Fetch {
1287+
with_ties: true,
1288+
percent: false,
1289+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1290+
})
1291+
);
1292+
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY");
1293+
assert_eq!(
1294+
ast.fetch,
1295+
Some(Fetch {
1296+
with_ties: false,
1297+
percent: true,
1298+
quantity: Some(ASTNode::SQLValue(Value::Long(50))),
1299+
})
1300+
);
1301+
let ast = verified_query(
1302+
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY",
1303+
);
1304+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1305+
assert_eq!(
1306+
ast.fetch,
1307+
Some(Fetch {
1308+
with_ties: false,
1309+
percent: false,
1310+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1311+
})
1312+
);
1313+
let ast = verified_query(
1314+
"SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY",
1315+
);
1316+
assert_eq!(
1317+
ast.fetch,
1318+
Some(Fetch {
1319+
with_ties: false,
1320+
percent: false,
1321+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1322+
})
1323+
);
1324+
match ast.body {
1325+
SQLSetExpr::Select(s) => match s.relation {
1326+
Some(TableFactor::Derived { subquery, .. }) => {
1327+
assert_eq!(
1328+
subquery.fetch,
1329+
Some(Fetch {
1330+
with_ties: false,
1331+
percent: false,
1332+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1333+
})
1334+
);
1335+
}
1336+
_ => panic!("Test broke"),
1337+
},
1338+
_ => panic!("Test broke"),
1339+
}
1340+
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");
1341+
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1342+
assert_eq!(
1343+
ast.fetch,
1344+
Some(Fetch {
1345+
with_ties: false,
1346+
percent: false,
1347+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1348+
})
1349+
);
1350+
match ast.body {
1351+
SQLSetExpr::Select(s) => match s.relation {
1352+
Some(TableFactor::Derived { subquery, .. }) => {
1353+
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
1354+
assert_eq!(
1355+
subquery.fetch,
1356+
Some(Fetch {
1357+
with_ties: false,
1358+
percent: false,
1359+
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
1360+
})
1361+
);
1362+
}
1363+
_ => panic!("Test broke"),
1364+
},
1365+
_ => panic!("Test broke"),
1366+
}
1367+
}
1368+
1369+
#[test]
1370+
fn parse_fetch_variations() {
1371+
one_statement_parses_to(
1372+
"SELECT foo FROM bar FETCH FIRST 10 ROW ONLY",
1373+
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
1374+
);
1375+
one_statement_parses_to(
1376+
"SELECT foo FROM bar FETCH NEXT 10 ROW ONLY",
1377+
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
1378+
);
1379+
one_statement_parses_to(
1380+
"SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES",
1381+
"SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES",
1382+
);
1383+
one_statement_parses_to(
1384+
"SELECT foo FROM bar FETCH NEXT ROWS WITH TIES",
1385+
"SELECT foo FROM bar FETCH FIRST ROWS WITH TIES",
1386+
);
1387+
one_statement_parses_to(
1388+
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
1389+
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
1390+
);
1391+
}
1392+
12121393
#[test]
12131394
#[should_panic(
12141395
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"

0 commit comments

Comments
 (0)