Skip to content

Add FETCH/OFFSET and JOIN LATERAL (subquery) #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/dialect/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ define_keywords!(
EXTRACT,
FALSE,
FETCH,
FIRST,
FILTER,
FIRST_VALUE,
FLOAT,
Expand Down Expand Up @@ -229,6 +230,7 @@ define_keywords!(
NATURAL,
NCHAR,
NCLOB,
NEXT,
NEW,
NO,
NONE,
Expand Down Expand Up @@ -341,6 +343,7 @@ define_keywords!(
TABLESAMPLE,
TEXT,
THEN,
TIES,
TIME,
TIMESTAMP,
TIMEZONE_HOUR,
Expand Down Expand Up @@ -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`
Expand Down
4 changes: 2 additions & 2 deletions src/sqlast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
50 changes: 47 additions & 3 deletions src/sqlast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -198,6 +209,7 @@ pub enum TableFactor {
with_hints: Vec<ASTNode>,
},
Derived {
lateral: bool,
subquery: Box<SQLQuery>,
alias: Option<SQLIdent>,
},
Expand All @@ -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);
}
Expand Down Expand Up @@ -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)
}
}
}
91 changes: 90 additions & 1 deletion src/sqlparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
})
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
Loading