Merge pull request #69 from thomas-jeepe/master

Add FETCH and OFFSET support, and LATERAL <derived table>
This commit is contained in:
Nickolay Ponomarev 2019-06-02 13:43:39 +03:00 committed by GitHub
commit 721c24188a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 368 additions and 7 deletions

View file

@ -155,6 +155,7 @@ define_keywords!(
EXTRACT, EXTRACT,
FALSE, FALSE,
FETCH, FETCH,
FIRST,
FILTER, FILTER,
FIRST_VALUE, FIRST_VALUE,
FLOAT, FLOAT,
@ -229,6 +230,7 @@ define_keywords!(
NATURAL, NATURAL,
NCHAR, NCHAR,
NCLOB, NCLOB,
NEXT,
NEW, NEW,
NO, NO,
NONE, NONE,
@ -341,6 +343,7 @@ define_keywords!(
TABLESAMPLE, TABLESAMPLE,
TEXT, TEXT,
THEN, THEN,
TIES,
TIME, TIME,
TIMESTAMP, TIMESTAMP,
TIMEZONE_HOUR, TIMEZONE_HOUR,
@ -396,7 +399,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[&str] = &[
// Reserved as both a table and a column alias: // Reserved as both a table and a column alias:
WITH, SELECT, WHERE, GROUP, ORDER, UNION, EXCEPT, INTERSECT, WITH, SELECT, WHERE, GROUP, ORDER, UNION, EXCEPT, INTERSECT,
// Reserved only as a table alias in the `FROM`/`JOIN` clauses: // 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` /// Can't be used as a column alias, so that `SELECT <expr> alias`

View file

@ -21,8 +21,8 @@ mod table_key;
mod value; mod value;
pub use self::query::{ pub use self::query::{
Cte, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect, SQLSelectItem, Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect,
SQLSetExpr, SQLSetOperator, TableFactor, SQLSelectItem, SQLSetExpr, SQLSetOperator, TableFactor,
}; };
pub use self::sqltype::SQLType; pub use self::sqltype::SQLType;
pub use self::table_key::{AlterOperation, Key, TableKey}; pub use self::table_key::{AlterOperation, Key, TableKey};

View file

@ -10,8 +10,12 @@ pub struct SQLQuery {
pub body: SQLSetExpr, pub body: SQLSetExpr,
/// ORDER BY /// ORDER BY
pub order_by: Vec<SQLOrderByExpr>, pub order_by: Vec<SQLOrderByExpr>,
/// LIMIT /// LIMIT { <N> | ALL }
pub limit: Option<ASTNode>, 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 { impl ToString for SQLQuery {
@ -27,6 +31,13 @@ impl ToString for SQLQuery {
if let Some(ref limit) = self.limit { if let Some(ref limit) = self.limit {
s += &format!(" LIMIT {}", limit.to_string()); 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 s
} }
} }
@ -198,6 +209,7 @@ pub enum TableFactor {
with_hints: Vec<ASTNode>, with_hints: Vec<ASTNode>,
}, },
Derived { Derived {
lateral: bool,
subquery: Box<SQLQuery>, subquery: Box<SQLQuery>,
alias: Option<SQLIdent>, alias: Option<SQLIdent>,
}, },
@ -224,8 +236,16 @@ impl ToString for TableFactor {
} }
s s
} }
TableFactor::Derived { subquery, alias } => { TableFactor::Derived {
let mut s = format!("({})", subquery.to_string()); lateral,
subquery,
alias,
} => {
let mut s = String::new();
if *lateral {
s += "LATERAL ";
}
s += &format!("({})", subquery.to_string());
if let Some(alias) = alias { if let Some(alias) = alias {
s += &format!(" AS {}", 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)
}
}
}

View file

@ -684,6 +684,40 @@ impl Parser {
true 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 /// 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> { pub fn expect_keyword(&mut self, expected: &'static str) -> Result<(), ParserError> {
if self.parse_keyword(expected) { if self.parse_keyword(expected) {
@ -1279,11 +1313,25 @@ impl Parser {
None 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 { Ok(SQLQuery {
ctes, ctes,
body, body,
limit, limit,
order_by, order_by,
offset,
fetch,
}) })
} }
@ -1416,11 +1464,18 @@ impl Parser {
/// A table name or a parenthesized subquery, followed by optional `[AS] alias` /// A table name or a parenthesized subquery, followed by optional `[AS] alias`
pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> { pub fn parse_table_factor(&mut self) -> Result<TableFactor, ParserError> {
let lateral = self.parse_keyword("LATERAL");
if self.consume_token(&Token::LParen) { if self.consume_token(&Token::LParen) {
let subquery = Box::new(self.parse_query()?); let subquery = Box::new(self.parse_query()?);
self.expect_token(&Token::RParen)?; self.expect_token(&Token::RParen)?;
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; 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 { } else {
let name = self.parse_object_name()?; let name = self.parse_object_name()?;
// Postgres, MSSQL: table-valued functions: // Postgres, MSSQL: table-valued functions:
@ -1655,6 +1710,40 @@ impl Parser {
.map(|n| Some(ASTNode::SQLValue(Value::Long(n)))) .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 { impl SQLWord {

View file

@ -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] #[test]
#[should_panic( #[should_panic(
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect" expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"