mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-31 19:27:21 +00:00
Merge pull request #69 from thomas-jeepe/master
Add FETCH and OFFSET support, and LATERAL <derived table>
This commit is contained in:
commit
721c24188a
5 changed files with 368 additions and 7 deletions
|
@ -155,6 +155,7 @@ define_keywords!(
|
|||
EXTRACT,
|
||||
FALSE,
|
||||
FETCH,
|
||||
FIRST,
|
||||
FILTER,
|
||||
FIRST_VALUE,
|
||||
FLOAT,
|
||||
|
@ -229,6 +230,7 @@ define_keywords!(
|
|||
NATURAL,
|
||||
NCHAR,
|
||||
NCLOB,
|
||||
NEXT,
|
||||
NEW,
|
||||
NO,
|
||||
NONE,
|
||||
|
@ -341,6 +343,7 @@ define_keywords!(
|
|||
TABLESAMPLE,
|
||||
TEXT,
|
||||
THEN,
|
||||
TIES,
|
||||
TIME,
|
||||
TIMESTAMP,
|
||||
TIMEZONE_HOUR,
|
||||
|
@ -396,7 +399,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
|
|||
// Reserved as both a table and a column alias:
|
||||
WITH, SELECT, WHERE, GROUP, ORDER, UNION, EXCEPT, INTERSECT,
|
||||
// Reserved only as a table alias in the `FROM`/`JOIN` clauses:
|
||||
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT,
|
||||
ON, JOIN, INNER, CROSS, FULL, LEFT, RIGHT, NATURAL, USING, LIMIT, OFFSET, FETCH,
|
||||
];
|
||||
|
||||
/// Can't be used as a column alias, so that `SELECT <expr> alias`
|
||||
|
|
|
@ -21,8 +21,8 @@ mod table_key;
|
|||
mod value;
|
||||
|
||||
pub use self::query::{
|
||||
Cte, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, SQLSelectItem,
|
||||
SQLSetExpr, SQLSetOperator, TableFactor,
|
||||
Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect,
|
||||
SQLSelectItem, SQLSetExpr, SQLSetOperator, TableFactor,
|
||||
};
|
||||
pub use self::sqltype::SQLType;
|
||||
pub use self::table_key::{AlterOperation, Key, TableKey};
|
||||
|
|
|
@ -10,8 +10,12 @@ pub struct SQLQuery {
|
|||
pub body: SQLSetExpr,
|
||||
/// ORDER BY
|
||||
pub order_by: Vec<SQLOrderByExpr>,
|
||||
/// LIMIT
|
||||
/// LIMIT { <N> | ALL }
|
||||
pub limit: Option<ASTNode>,
|
||||
/// OFFSET <N> { ROW | ROWS }
|
||||
pub offset: Option<ASTNode>,
|
||||
/// FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }
|
||||
pub fetch: Option<Fetch>,
|
||||
}
|
||||
|
||||
impl ToString for SQLQuery {
|
||||
|
@ -27,6 +31,13 @@ impl ToString for SQLQuery {
|
|||
if let Some(ref limit) = self.limit {
|
||||
s += &format!(" LIMIT {}", limit.to_string());
|
||||
}
|
||||
if let Some(ref offset) = self.offset {
|
||||
s += &format!(" OFFSET {} ROWS", offset.to_string());
|
||||
}
|
||||
if let Some(ref fetch) = self.fetch {
|
||||
s.push(' ');
|
||||
s += &fetch.to_string();
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +209,7 @@ pub enum TableFactor {
|
|||
with_hints: Vec<ASTNode>,
|
||||
},
|
||||
Derived {
|
||||
lateral: bool,
|
||||
subquery: Box<SQLQuery>,
|
||||
alias: Option<SQLIdent>,
|
||||
},
|
||||
|
@ -224,8 +236,16 @@ impl ToString for TableFactor {
|
|||
}
|
||||
s
|
||||
}
|
||||
TableFactor::Derived { subquery, alias } => {
|
||||
let mut s = format!("({})", subquery.to_string());
|
||||
TableFactor::Derived {
|
||||
lateral,
|
||||
subquery,
|
||||
alias,
|
||||
} => {
|
||||
let mut s = String::new();
|
||||
if *lateral {
|
||||
s += "LATERAL ";
|
||||
}
|
||||
s += &format!("({})", subquery.to_string());
|
||||
if let Some(alias) = alias {
|
||||
s += &format!(" AS {}", alias);
|
||||
}
|
||||
|
@ -320,3 +340,27 @@ impl ToString for SQLOrderByExpr {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Fetch {
|
||||
pub with_ties: bool,
|
||||
pub percent: bool,
|
||||
pub quantity: Option<ASTNode>,
|
||||
}
|
||||
|
||||
impl ToString for Fetch {
|
||||
fn to_string(&self) -> String {
|
||||
let extension = if self.with_ties { "WITH TIES" } else { "ONLY" };
|
||||
if let Some(ref quantity) = self.quantity {
|
||||
let percent = if self.percent { " PERCENT" } else { "" };
|
||||
format!(
|
||||
"FETCH FIRST {}{} ROWS {}",
|
||||
quantity.to_string(),
|
||||
percent,
|
||||
extension
|
||||
)
|
||||
} else {
|
||||
format!("FETCH FIRST ROWS {}", extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -684,6 +684,40 @@ impl Parser {
|
|||
true
|
||||
}
|
||||
|
||||
/// Look for one of the given keywords and return the one that matches.
|
||||
#[must_use]
|
||||
pub fn parse_one_of_keywords(&mut self, keywords: &[&'static str]) -> Option<&'static str> {
|
||||
for keyword in keywords {
|
||||
assert!(keywords::ALL_KEYWORDS.contains(keyword));
|
||||
}
|
||||
match self.peek_token() {
|
||||
Some(Token::SQLWord(ref k)) => keywords
|
||||
.iter()
|
||||
.find(|keyword| keyword.eq_ignore_ascii_case(&k.keyword))
|
||||
.map(|keyword| {
|
||||
self.next_token();
|
||||
*keyword
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Bail out if the current token is not one of the expected keywords, or consume it if it is
|
||||
#[must_use]
|
||||
pub fn expect_one_of_keywords(
|
||||
&mut self,
|
||||
keywords: &[&'static str],
|
||||
) -> Result<&'static str, ParserError> {
|
||||
if let Some(keyword) = self.parse_one_of_keywords(keywords) {
|
||||
Ok(keyword)
|
||||
} else {
|
||||
self.expected(
|
||||
&format!("one of {}", keywords.join(" or ")),
|
||||
self.peek_token(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Bail out if the current token is not an expected keyword, or consume it if it is
|
||||
pub fn expect_keyword(&mut self, expected: &'static str) -> Result<(), ParserError> {
|
||||
if self.parse_keyword(expected) {
|
||||
|
@ -1279,11 +1313,25 @@ impl Parser {
|
|||
None
|
||||
};
|
||||
|
||||
let offset = if self.parse_keyword("OFFSET") {
|
||||
Some(self.parse_offset()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let fetch = if self.parse_keyword("FETCH") {
|
||||
Some(self.parse_fetch()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(SQLQuery {
|
||||
ctes,
|
||||
body,
|
||||
limit,
|
||||
order_by,
|
||||
offset,
|
||||
fetch,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1416,11 +1464,18 @@ impl Parser {
|
|||
|
||||
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`
|
||||
pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> {
|
||||
let lateral = self.parse_keyword("LATERAL");
|
||||
if self.consume_token(&Token::LParen) {
|
||||
let subquery = Box::new(self.parse_query()?);
|
||||
self.expect_token(&Token::RParen)?;
|
||||
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
|
||||
Ok(TableFactor::Derived { subquery, alias })
|
||||
Ok(TableFactor::Derived {
|
||||
lateral,
|
||||
subquery,
|
||||
alias,
|
||||
})
|
||||
} else if lateral {
|
||||
self.expected("subquery after LATERAL", self.peek_token())
|
||||
} else {
|
||||
let name = self.parse_object_name()?;
|
||||
// Postgres, MSSQL: table-valued functions:
|
||||
|
@ -1655,6 +1710,40 @@ impl Parser {
|
|||
.map(|n| Some(ASTNode::SQLValue(Value::Long(n))))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an OFFSET clause
|
||||
pub fn parse_offset(&mut self) -> Result<ASTNode, ParserError> {
|
||||
let value = self
|
||||
.parse_literal_int()
|
||||
.map(|n| ASTNode::SQLValue(Value::Long(n)))?;
|
||||
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Parse a FETCH clause
|
||||
pub fn parse_fetch(&mut self) -> Result<Fetch, ParserError> {
|
||||
self.expect_one_of_keywords(&["FIRST", "NEXT"])?;
|
||||
let (quantity, percent) = if self.parse_one_of_keywords(&["ROW", "ROWS"]).is_some() {
|
||||
(None, false)
|
||||
} else {
|
||||
let quantity = self.parse_sql_value()?;
|
||||
let percent = self.parse_keyword("PERCENT");
|
||||
self.expect_one_of_keywords(&["ROW", "ROWS"])?;
|
||||
(Some(quantity), percent)
|
||||
};
|
||||
let with_ties = if self.parse_keyword("ONLY") {
|
||||
false
|
||||
} else if self.parse_keywords(vec!["WITH", "TIES"]) {
|
||||
true
|
||||
} else {
|
||||
return self.expected("one of ONLY or WITH TIES", self.peek_token());
|
||||
};
|
||||
Ok(Fetch {
|
||||
with_ties,
|
||||
percent,
|
||||
quantity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SQLWord {
|
||||
|
|
|
@ -1388,6 +1388,231 @@ fn parse_invalid_subquery_without_parens() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_offset() {
|
||||
let ast = verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 OFFSET 2 ROWS");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
let ast = verified_query("SELECT foo FROM bar ORDER BY baz OFFSET 2 ROWS");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS) OFFSET 2 ROWS");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
match ast.body {
|
||||
SQLSetExpr::Select(s) => match s.relation {
|
||||
Some(TableFactor::Derived { subquery, .. }) => {
|
||||
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
}
|
||||
_ => panic!("Test broke"),
|
||||
},
|
||||
_ => panic!("Test broke"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_singular_row_offset() {
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar OFFSET 1 ROW",
|
||||
"SELECT foo FROM bar OFFSET 1 ROWS",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_fetch() {
|
||||
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY");
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY");
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: None,
|
||||
})
|
||||
);
|
||||
let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY");
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY");
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query(
|
||||
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES",
|
||||
);
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: true,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY");
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: true,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(50))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query(
|
||||
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY",
|
||||
);
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
let ast = verified_query(
|
||||
"SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY",
|
||||
);
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
match ast.body {
|
||||
SQLSetExpr::Select(s) => match s.relation {
|
||||
Some(TableFactor::Derived { subquery, .. }) => {
|
||||
assert_eq!(
|
||||
subquery.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => panic!("Test broke"),
|
||||
},
|
||||
_ => panic!("Test broke"),
|
||||
}
|
||||
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");
|
||||
assert_eq!(ast.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
assert_eq!(
|
||||
ast.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
match ast.body {
|
||||
SQLSetExpr::Select(s) => match s.relation {
|
||||
Some(TableFactor::Derived { subquery, .. }) => {
|
||||
assert_eq!(subquery.offset, Some(ASTNode::SQLValue(Value::Long(2))));
|
||||
assert_eq!(
|
||||
subquery.fetch,
|
||||
Some(Fetch {
|
||||
with_ties: false,
|
||||
percent: false,
|
||||
quantity: Some(ASTNode::SQLValue(Value::Long(2))),
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => panic!("Test broke"),
|
||||
},
|
||||
_ => panic!("Test broke"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_fetch_variations() {
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar FETCH FIRST 10 ROW ONLY",
|
||||
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
|
||||
);
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar FETCH NEXT 10 ROW ONLY",
|
||||
"SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY",
|
||||
);
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES",
|
||||
"SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES",
|
||||
);
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar FETCH NEXT ROWS WITH TIES",
|
||||
"SELECT foo FROM bar FETCH FIRST ROWS WITH TIES",
|
||||
);
|
||||
one_statement_parses_to(
|
||||
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
|
||||
"SELECT foo FROM bar FETCH FIRST ROWS ONLY",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lateral_derived() {
|
||||
fn chk(lateral_in: bool) {
|
||||
let lateral_str = if lateral_in { "LATERAL " } else { "" };
|
||||
let sql = format!(
|
||||
"SELECT * FROM customer LEFT JOIN {}\
|
||||
(SELECT * FROM order WHERE order.customer = customer.id LIMIT 3) AS order ON true",
|
||||
lateral_str
|
||||
);
|
||||
let select = verified_only_select(&sql);
|
||||
assert_eq!(select.joins.len(), 1);
|
||||
assert_eq!(
|
||||
select.joins[0].join_operator,
|
||||
JoinOperator::LeftOuter(JoinConstraint::On(ASTNode::SQLValue(Value::Boolean(true))))
|
||||
);
|
||||
if let TableFactor::Derived {
|
||||
lateral,
|
||||
ref subquery,
|
||||
ref alias,
|
||||
} = select.joins[0].relation
|
||||
{
|
||||
assert_eq!(lateral_in, lateral);
|
||||
assert_eq!(Some("order".to_string()), *alias);
|
||||
assert_eq!(
|
||||
subquery.to_string(),
|
||||
"SELECT * FROM order WHERE order.customer = customer.id LIMIT 3"
|
||||
);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
chk(false);
|
||||
chk(true);
|
||||
|
||||
let sql = "SELECT * FROM customer LEFT JOIN LATERAL generate_series(1, customer.id)";
|
||||
let res = parse_sql_statements(sql);
|
||||
assert_eq!(
|
||||
ParserError::ParserError(
|
||||
"Expected subquery after LATERAL, found: generate_series".to_string()
|
||||
),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue