Skip to content

Commit

Permalink
Support from first in select fix #1400
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Sep 14, 2024
1 parent 4d52ee7 commit 052d041
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 72 deletions.
19 changes: 16 additions & 3 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::ast::*;

/// The most complete variant of a `SELECT` query expression, optionally
/// including `WITH`, `UNION` / other set operations, and `ORDER BY`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
#[cfg_attr(feature = "visitor", visit(with = "visit_query"))]
Expand Down Expand Up @@ -150,6 +150,13 @@ pub enum SetExpr {
Table(Box<Table>),
}

impl Default for SetExpr {
fn default() -> Self {
SetExpr::Select(Box::new(Select::default()))
}

}

impl SetExpr {
/// If this `SetExpr` is a `SELECT`, returns the [`Select`].
pub fn as_select(&self) -> Option<&Select> {
Expand Down Expand Up @@ -267,7 +274,7 @@ impl fmt::Display for Table {
/// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may
/// appear either as the only body item of a `Query`, or as an operand
/// to a set operation like `UNION`.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Select {
Expand Down Expand Up @@ -2106,7 +2113,13 @@ pub enum GroupByExpr {
Expressions(Vec<Expr>, Vec<GroupByWithModifier>),
}

impl fmt::Display for GroupByExpr {
impl Default for GroupByExpr {
fn default() -> Self {
GroupByExpr::Expressions(vec![], vec![])
}
}

impl Display for GroupByExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GroupByExpr::All(modifiers) => {
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ impl Dialect for DuckDbDialect {
fn support_map_literal_syntax(&self) -> bool {
true
}

/// DuckDB allows this.
///
/// https://duckdb.org/2023/08/23/even-friendlier-sql.html#from-first-in-select-statements
fn allow_from_first(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,8 @@ impl Dialect for GenericDialect {
fn supports_create_index_with_clause(&self) -> bool {
true
}

fn allow_from_first(&self) -> bool {
true
}
}
6 changes: 6 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,12 @@ pub trait Dialect: Debug + Any {
fn supports_create_index_with_clause(&self) -> bool {
false
}

/// Whether to allow `SELECT` statements that start with a `FROM` clause,
/// e.g. `FROM x SELECT foo` see <https://github.com/sqlparser-rs/sqlparser-rs/issues/1400> for rationale.
fn allow_from_first(&self) -> bool {
false
}
}

/// This represents the operators for which precedence must be defined
Expand Down
181 changes: 112 additions & 69 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ impl<'a> Parser<'a> {
self.prev_token();
self.parse_boxed_query().map(Statement::Query)
}
Keyword::FROM if self.dialect.allow_from_first() => {
Ok(Statement::Query(self.parse_from_first_query(None).map(Box::new)?))
}
Keyword::TRUNCATE => self.parse_truncate(),
Keyword::ATTACH => {
if dialect_of!(self is DuckDbDialect) {
Expand Down Expand Up @@ -8391,91 +8394,131 @@ impl<'a> Parser<'a> {
settings: None,
format_clause: None,
})
} else if self.parse_keyword(Keyword::FROM) && self.dialect.allow_from_first() {
self.parse_from_first_query(with)
} else {
let body = self.parse_boxed_query_body(self.dialect.prec_unknown())?;
self.parse_query_extra(body, with)
}
}

let order_by = self.parse_optional_order_by()?;

let mut limit = None;
let mut offset = None;
/// Parse `SELECT` statements that start with a `FROM` clause,
/// e.g. `FROM x SELECT foo` see <https://github.com/sqlparser-rs/sqlparser-rs/issues/1400> for rationale.
fn parse_from_first_query(&mut self, with: Option<With>) -> Result<Query, ParserError> {
let from = self.parse_comma_separated(Parser::parse_table_and_joins)?;

for _x in 0..2 {
if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
limit = self.parse_limit()?
if matches!(self.peek_token().token, Token::EOF | Token::SemiColon) {
// no select part e.g. `FROM x`, this is equivalent to `FROM x SELECT *`
return Ok(Query {
with,
body: Box::new(SetExpr::Select(Box::new(Select {
projection: vec![
SelectItem::Wildcard(WildcardAdditionalOptions::default()),
],
from,
..Default::default()
}))),
..Default::default()
});
} else {
let body = self.parse_boxed_query_body(self.dialect.prec_unknown())?;
let mut query = self.parse_query_extra(body, with)?;
if let SetExpr::Select(ref mut select) = *query.body {
if select.from.is_empty() {
select.from = from;
Ok(query)
} else {
Err(ParserError::ParserError("FROM clause can only be used once".to_string()))
}
} else {
Err(ParserError::ParserError("leading FROM clause can only be used with SELECT".to_string()))
}
}
}

if offset.is_none() && self.parse_keyword(Keyword::OFFSET) {
offset = Some(self.parse_offset()?)
}
/// Parse everything beyond the body of a query, i.e. everything after the `SELECT` clause
fn parse_query_extra(&mut self, body: Box<SetExpr>, with: Option<With>) -> Result<Query, ParserError> {
let order_by = self.parse_optional_order_by()?;

if dialect_of!(self is GenericDialect | MySqlDialect | ClickHouseDialect)
&& limit.is_some()
&& offset.is_none()
&& self.consume_token(&Token::Comma)
{
// MySQL style LIMIT x,y => LIMIT y OFFSET x.
// Check <https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
offset = Some(Offset {
value: limit.unwrap(),
rows: OffsetRows::None,
});
limit = Some(self.parse_expr()?);
}
let mut limit = None;
let mut offset = None;

for _x in 0..2 {
if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
limit = self.parse_limit()?
}

if offset.is_none() && self.parse_keyword(Keyword::OFFSET) {
offset = Some(self.parse_offset()?)
}

let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.parse_keyword(Keyword::BY)
if dialect_of!(self is GenericDialect | MySqlDialect | ClickHouseDialect)
&& limit.is_some()
&& offset.is_none()
&& self.consume_token(&Token::Comma)
{
self.parse_comma_separated(Parser::parse_expr)?
} else {
vec![]
};
// MySQL style LIMIT x,y => LIMIT y OFFSET x.
// Check <https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
offset = Some(Offset {
value: limit.unwrap(),
rows: OffsetRows::None,
});
limit = Some(self.parse_expr()?);
}
}

let settings = self.parse_settings()?;
let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.parse_keyword(Keyword::BY)
{
self.parse_comma_separated(Parser::parse_expr)?
} else {
vec![]
};

let fetch = if self.parse_keyword(Keyword::FETCH) {
Some(self.parse_fetch()?)
} else {
None
};
let settings = self.parse_settings()?;

let mut for_clause = None;
let mut locks = Vec::new();
while self.parse_keyword(Keyword::FOR) {
if let Some(parsed_for_clause) = self.parse_for_clause()? {
for_clause = Some(parsed_for_clause);
break;
} else {
locks.push(self.parse_lock()?);
}
let fetch = if self.parse_keyword(Keyword::FETCH) {
Some(self.parse_fetch()?)
} else {
None
};

let mut for_clause = None;
let mut locks = Vec::new();
while self.parse_keyword(Keyword::FOR) {
if let Some(parsed_for_clause) = self.parse_for_clause()? {
for_clause = Some(parsed_for_clause);
break;
} else {
locks.push(self.parse_lock()?);
}
let format_clause = if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.parse_keyword(Keyword::FORMAT)
{
if self.parse_keyword(Keyword::NULL) {
Some(FormatClause::Null)
} else {
let ident = self.parse_identifier(false)?;
Some(FormatClause::Identifier(ident))
}
}
let format_clause = if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.parse_keyword(Keyword::FORMAT)
{
if self.parse_keyword(Keyword::NULL) {
Some(FormatClause::Null)
} else {
None
};
let ident = self.parse_identifier(false)?;
Some(FormatClause::Identifier(ident))
}
} else {
None
};

Ok(Query {
with,
body,
order_by,
limit,
limit_by,
offset,
fetch,
locks,
for_clause,
settings,
format_clause,
})
}
Ok(Query {
with,
body,
order_by,
limit,
limit_by,
offset,
fetch,
locks,
for_clause,
settings,
format_clause,
})
}

fn parse_settings(&mut self) -> Result<Option<Vec<Setting>>, ParserError> {
Expand Down

0 comments on commit 052d041

Please sign in to comment.